Menu

Reference / Async

CancelledException

finalclass
final class CancelledException extends \Error

Raised inside a task that was cancelled.

Cancellation is delivered at the task's current suspend point (a delay(), a channel wait, a blocking stream call, an await()) and is sticky: it is raised again at every later suspend point until the task ends. Catching it once therefore buys a task one more suspend, not immunity. Let it propagate, or run cleanup that must itself suspend inside shield().

It extends \Error, not \Exception, on purpose: the blanket catch (\Exception $e) most PHP code is written with cannot swallow it. A scope that cancelled its own children does not count their CancelledException as a failure.

try {
    delay(10.0);
} catch (CancelledException $e) {
    shield(fn() => $conn->close());   // cleanup that suspends
    throw $e;                          // always rethrow
}

Example

use Async\CancelledException;
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
use function Async\group;

async(function () {
    group(function (TaskGroup $g) {
        $token = $g->token();
        $token->onCancel(fn() => print("onCancel hook ran\n"));

        // A task that suspends is interrupted with CancelledException,
        // which extends \Error: catch (\Exception) cannot swallow it.
        $g->spawn(function () {
            try {
                delay(10.0);
            } catch (CancelledException $e) {
                echo "sleeper cancelled\n";
                throw $e;
            }
        });

        // A loop that never suspends polls the token instead.
        $g->spawn(function () use ($g, $token) {
            for ($step = 1; $step <= 5; $step++) {
                if ($step === 3) {
                    $g->cancel();   // the write half; $token is the read-only view
                }
                if ($token->isCancelled()) {
                    echo "loop stopped at step $step\n";
                    return;
                }
                echo "step $step\n";
            }
        });
    });
    echo "scope joined\n";
});

Output

step 1
step 2
onCancel hook ran
loop stopped at step 3
sleeper cancelled
scope joined

See also: shield, Task::cancel, TaskGroup::cancel, CancellationToken

View source