Cancelling ReactPHP fibers
A feature that we really needed to make our fiber integration complete is the cancellation of them. Or to be more precise, the cancellation any awaited promise yielding operations in that fiber and as a consequence the fiber that those are awaited in. This post goes into detail how different cancelation scenarios work for the PR introducing it, and was originally part of that PR's documentation but was replaced by a simpler section.
Promises returned by the async
function can be cancelled and when done they will cancel any recursive async
call
and any currently awaited promise using the await
function. In the following example echo 'b';
will never
be reached, and the await
function at the bottom will also throw an exception with the following message
Timer cancelled
.
$promise = async(static function (): int {
echo 'a';
await(sleep(2));
echo 'b';
return time();
})();
$promise->cancel();
await($promise);
If you however decide to try and catch that await
you will reach echo 'b';
. The exception you caught however
isn't thrown by the bottom await function. Just as with synchronous code catching it lets you ignore the exception or
error that is thrown.
$promise = async(static function (): int {
echo 'a';
try {
await(sleep(2));
} catch (\Throwable) {
// No-Op
}
echo 'b';
return time();
})();
$promise->cancel();
await($promise);
When a fiber is cancelled, all currently pending and future awaited promises will be cancelled. As such the following
example will never output c
and a timeout exception will be thrown.
$promise = async(static function (): int {
echo 'a';
try {
await(sleep(2));
} catch (\Throwable) {
// No-Op
}
echo 'b';
await(sleep(0.1));
echo 'c';
return time();
})();
$promise->cancel();
await($promise);
Any nested async
and await
calls are also canceled. You can nest this as deep as you want. As long as you await
every promise yielding function you call. The following example will output abc
.
$promise = async(static function (): int {
echo 'a';
await(async(static function(): void {
echo 'b';
await(async(static function(): void {
echo 'c';
await(sleep(2));
echo 'd';
})());
echo 'e';
})());
echo 'f';
return time();
})();
$promise->cancel();
await($promise);
Be very much aware that if you call a promise yielding function and not await it, it will not be cancelled. The
following example will output acb
.
$promise = async(static function (): int {
echo 'a';
sleep(0.001)->then(static function (): void {
echo 'b';
});
echo 'c';
return time();
})();
$promise->cancel();
Conclusion
While this text didn't make the cut in the PR, as most of it is implied. I did feel it is important to put it out there for those looking for some more explicit clarity on this subject.