amphp / amp

A non-blocking concurrency framework for PHP applications. 🐘
https://amphp.org/amp
MIT License
4.25k stars 257 forks source link

Interception of pause / resume future #436

Closed joelwurtz closed 7 months ago

joelwurtz commented 7 months ago

Hey,

We are actually using amp to do non blocking integration test (a.k.a when a test use an non blocking io library it run other tests until the io call has been completed)

Here is an example:

class MyTest {
    public function test1() {
        $result = $this->request('/delay/3'); // it uses amphttp client 

        $this->assertTrue($result['foo']);
    }

    public function test2() {
        $result = $this->request('/delay/1'); // it uses amphttp client 

        $this->assertTrue($result['foo']);
    }
}

Running this test would actually takes 3 seconds (since it will be done concurrently)

Our implementation is pretty simple and just use a Pool of tests

while (!$pool->isEmpty()) {
    $test = $pool->getTestToRun();

    $futures[$test->getIdentifier()] = async(function () use ($test, &$futures) {
        $this->run($test);

        unset($futures[$test->getIdentifier()]);
    });
}

In this library we want to track number of assertion per test (in the example i want to track that test1 and test2 have one assertion)

So the goal is that each time a $this->assert*() method is called we increment the number in our test

Our problem is that when there is an io call that would launch a new test (test2 in this case) we do no longer have the correct reference to our test (the assert in the first test will be counted toward the second test)

I'm wondering if there is a possibility to "intercept" a paused future, so we can exec code each time it's paused or resumed due to an async call inside

Something like this :

$future = async(fn () => $this->run($test));
$future->intercept(onPause: fn () => {}, onResume: () => {
    $this->setCurrentRunningTest($test);
});
$future->await();
bwoebi commented 7 months ago

From what I read here, what you want is rather a way to track state depending on which Future is executed. I.e. a FiberLocal (https://github.com/revoltphp/event-loop/blob/main/src/EventLoop/FiberLocal.php).

That works at least as long as the assertions are all in the same fiber. If you have nested fibers containing assertions pertaining to a same test, I believe there's currently no solution for that. (i.e. context inherited storage)

joelwurtz commented 7 months ago

Oh nice, this works for my use case, and i don't think i will have too much nested fiber in my case, even in this case i can provide an helper to forward the storage to the underlying fiber when creating one.

Thanks a lot for the pointer, will close this then as not needed