PHPackages                             hiblaphp/cancellation - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. hiblaphp/cancellation

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

hiblaphp/cancellation
=====================

Abstraction for C# inspired Cancellation Token Source Implementation for PHP

0694↑85%1PHPCI passing

Since Mar 20Pushed 1mo agoCompare

[ Source](https://github.com/hiblaphp/cancellation)[ Packagist](https://packagist.org/packages/hiblaphp/cancellation)[ RSS](/packages/hiblaphp-cancellation/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (1)

Hibla Cancellation
==================

[](#hibla-cancellation)

**Structured external cancellation for the Hibla async ecosystem.**

A `CancellationToken` implementation that provides a shared cancellation signal for coordinating the cancellation of multiple independent promises and async operations from a single control point. Complements the built-in cancellation on `hiblaphp/promise` by adding external, user-driven cancellation that can span across unrelated promise chains.

[![Latest Release](https://camo.githubusercontent.com/ffd7679ff25ab16428d867ab54e3067522eec775299edd1909d614c27262c7da/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f6869626c617068702f63616e63656c6c6174696f6e2e7376673f7374796c653d666c61742d737175617265)](https://github.com/hiblaphp/cancellation/releases)[![MIT License](https://camo.githubusercontent.com/942e017bf0672002dd32a857c95d66f28c5900ab541838c6c664442516309c8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666c61742d737175617265)](./LICENSE)

---

Contents
--------

[](#contents)

**Getting started**

- [Installation](#installation)
- [Introduction](#introduction)
- [How it Relates to Promise Cancellation](#how-it-relates-to-promise-cancellation)

**Core usage**

- [Basic Usage](#basic-usage)
- [Accepting a Token in Your Functions](#accepting-a-token-in-your-functions)
    - [Polling with `isCancelled()`](#polling-with-iscancelled)
    - [Throwing with `throwIfCancelled()`](#throwing-with-throwifcancelled)
    - [Tracking promises with `track()`](#tracking-promises-with-track)
    - [Default parameter with `CancellationToken::none()`](#default-parameter-with-cancellationtokennone)
- [Cancellation is Synchronous](#cancellation-is-synchronous)
    - [Exceptions during cancellation](#exceptions-during-cancellation)

**Features**

- [Automatic Timeout](#automatic-timeout)
- [Linking Multiple Tokens](#linking-multiple-tokens)
- [Cleanup Registration with `onCancel()`](#cleanup-registration-with-oncancel)
- [Monitoring Tracked Promises](#monitoring-tracked-promises)
- [`cancel()` vs `cancelChain()`](#cancel-vs-cancelchain)
- [Integration with `await()`](#integration-with-await)
- [Resource Cleanup on Scope Exit](#resource-cleanup-on-scope-exit)

**Reference**

- [API Reference](#api-reference)
    - [`CancellationTokenSource`](#cancellationtokensource)
    - [`CancellationToken`](#cancellationtoken)
    - [`CancellationTokenRegistration`](#cancellationtokenregistration)

**Meta**

- [Development](#development)
- [Credits](#credits)
- [License](#license)

---

Installation
------------

[](#installation)

```
composer require hiblaphp/cancellation
```

**Requirements:**

- PHP 8.3+
- `hiblaphp/promise`
- `hiblaphp/event-loop`

---

Introduction
------------

[](#introduction)

`hiblaphp/promise` gives every promise its own built-in cancellation — `cancel()`, `cancelChain()`, and `onCancel()` are first-class features of every promise instance. That system works well for a single promise chain: cleanup is registered at the point where the resource is created, and cancellation propagates through the chain automatically.

But that model is low-level. It works on individual chains, and it requires you to hold a reference to the right promise at the right time. As soon as your application grows beyond a single chain, the cracks start to show.

Consider a typical async workflow — fetching a user, their orders, and generating a report, all running concurrently. Now a user clicks abort. To cancel everything you need a reference to every root promise in every chain. If those chains were created in different functions, passed across layers, or started at different times, gathering those references is fragile and invasive. The alternative — threading a shared flag or a nullable promise through every function signature — couples your entire call graph to a concern that most of it should not know about.

```
// Without CancellationToken — every layer must be aware of cancellation
function buildReport(
    int $userId,
    ?PromiseInterface &$userPromise,    // ← caller needs this reference
    ?PromiseInterface &$ordersPromise,  // ← and this one
    ?PromiseInterface &$reportPromise   // ← and this one
): PromiseInterface {
    $userPromise   = fetchUser($userId);
    $ordersPromise = fetchOrders($userId);
    $reportPromise = Promise::all([$userPromise, $ordersPromise])
        ->then(fn($r) => generateReport(...$r));

    return $reportPromise;
}

// Caller has to manually cancel each one
$userPromise->cancel();
$ordersPromise->cancel();
$reportPromise->cancel();
```

`hiblaphp/cancellation` solves this with the `CancellationToken` pattern, inspired by .NET's `CancellationToken` and `CancellationTokenSource`. The idea is a producer/consumer split:

- The **`CancellationTokenSource`** owns the ability to cancel. You hold it at the control point — the button handler, the timeout, the shutdown hook.
- The **`CancellationToken`** is a read-only view of the cancellation signal. You pass it into operations or track promises against it. Operations never receive the source, only the token.

When you cancel the source, every promise tracked against that token is cancelled together — triggering their individual `onCancel()` handlers and freeing their resources — regardless of whether those promises share a chain or were created in completely different parts of your application.

```
// With CancellationToken — one signal cancels everything
$cts = new CancellationTokenSource();

$reportPromise = buildReport(1);
$cts->token->track($reportPromise);

// One call cancels the entire operation
$cts->cancel();
// -> $reportPromise cancelled
// -> Promise::all() cancels siblings synchronously
// -> their onCancel() handlers free HTTP connections
// -> buildReport() never needed to know about the token
```

This design means functions in your codebase do not need to accept and thread a `CancellationToken` through every layer. A function that creates a promise already has its cleanup registered via `onCancel()` at the point of creation. All the token needs to do is reach the returned promise and call `cancel()`on it — the cleanup chain fires correctly regardless of how deep in the call stack the promise was created. The promise chain carries its own cleanup. The token is just the trigger.

---

How it Relates to Promise Cancellation
--------------------------------------

[](#how-it-relates-to-promise-cancellation)

`hiblaphp/promise` has built-in cancellation on every promise — it is self-contained and does not require this library. `hiblaphp/cancellation`sits on top of that system and adds external coordination.

```
Built-in promise cancellation:
  $promise->cancel()
    → $promise->onCancel() handlers fire
    → child promises cancelled via forward propagation
    → resources freed

CancellationToken coordination:
  $cts->cancel()
    → token->onCancel() callbacks fire
    → token->track($promise) → $promise->cancel() fires
    → $promise->onCancel() handlers fire
    → resources freed

```

Use promise cancellation to encapsulate cleanup inside the function that creates a resource. Use `CancellationToken` when you need one external signal to coordinate cancellation across multiple independent operations — user-initiated abort, timeout coordination, or group cancellation where gathering individual promise references would be invasive or impractical.

---

Basic Usage
-----------

[](#basic-usage)

The token is accessed via the `readonly` public property `$token` on the source — there is no getter method:

```
use Hibla\Cancellation\CancellationTokenSource;

$cts   = new CancellationTokenSource();
$token = $cts->token; // readonly public property

// Pass the token into operations
$promise1 = fetchUser(1, $token);
$promise2 = fetchOrders(1, $token);

// Cancel everything from a single control point
$cts->cancel();
```

Calling `cancel()` multiple times on the same source is safe and idempotent — subsequent calls have no effect:

```
$cts->cancel(); // cancels
$cts->cancel(); // no-op — already cancelled
$cts->cancel(); // no-op
```

---

Accepting a Token in Your Functions
-----------------------------------

[](#accepting-a-token-in-your-functions)

Functions that want to support external cancellation accept a `CancellationToken` and use it to either check for cancellation at safe checkpoints or to track the promises they create.

### Polling with `isCancelled()`

[](#polling-with-iscancelled)

Use `isCancelled()` for non-throwing checks inside loops or before starting expensive work:

```
use Hibla\Cancellation\CancellationToken;

function processItems(array $items, CancellationToken $token): void
{
    foreach ($items as $item) {
        if ($token->isCancelled()) {
            return; // stop gracefully
        }

        processItem($item);
    }
}
```

### Throwing with `throwIfCancelled()`

[](#throwing-with-throwifcancelled)

`throwIfCancelled()` is the preferred pattern for long-running work because it surfaces cancellation as a `CancelledException` that unwinds naturally through `try/finally` blocks, ensuring cleanup always runs:

```
function processItems(array $items, CancellationToken $token): void
{
    $resource = openResource();

    try {
        foreach ($items as $item) {
            $token->throwIfCancelled(); // throws CancelledException if cancelled

            processItem($item);
        }
    } finally {
        $resource->close(); // always runs — even when cancelled
    }
}
```

### Tracking promises with `track()`

[](#tracking-promises-with-track)

Use `track()` to register a promise for automatic cancellation when the token is cancelled. The promise is automatically untracked when it settles — fulfilled, rejected, or cancelled — so no manual cleanup is ever needed:

```
use Hibla\Cancellation\CancellationToken;
use Hibla\Promise\Interfaces\PromiseInterface;

function fetchUser(int $id, CancellationToken $token): PromiseInterface
{
    $promise = Http::get("/users/$id");

    // When $token is cancelled, $promise is automatically cancelled.
    // This triggers $promise's own onCancel() handlers — the HTTP
    // request is aborted via whatever cleanup was registered there.
    $token->track($promise);

    return $promise;
}
```

Tracking an already-settled or already-cancelled promise is a safe no-op — `track()` checks the promise state and returns immediately if it has already completed. Promises are untracked automatically on settlement, covering all three outcomes — you never need to call `untrack()` after a promise completes:

```
$promise = $token->track(fetchUser(1));

$token->getTrackedCount(); // 1

// After the promise fulfills, rejects, or is cancelled:
$token->getTrackedCount(); // 0 — automatically untracked
```

Manual `untrack()` is only needed when you want to detach a still-pending promise from the token before it settles — for example, promoting an operation to run independently after a user cancels everything else.

### Default parameter with `CancellationToken::none()`

[](#default-parameter-with-cancellationtokennone)

`CancellationToken::none()` returns a shared singleton token that can never be cancelled. All token methods work correctly on it without any null checks or guards — `isCancelled()` always returns false, `throwIfCancelled()` never throws, `track()` is a safe no-op, and `onCancel()` returns a pre-disposed registration without storing the callback:

```
use Hibla\Cancellation\CancellationToken;

function fetchUser(
    int $id,
    CancellationToken $token = null
): PromiseInterface {
    $token ??= CancellationToken::none();

    $promise = Http::get("/users/$id");
    $token->track($promise);     // safe — no-op on none(), nothing stored
    $token->isCancelled();       // safe — always false
    $token->throwIfCancelled();  // safe — never throws

    return $promise;
}

// Works with or without a token
$user = fetchUser(1)->wait();
$user = fetchUser(1, $cts->token)->wait();
```

Calling `onCancel()` on `none()` returns a pre-disposed registration without storing the callback — the callback will never fire and nothing is retained against the singleton:

```
$token = CancellationToken::none();

$registration = $token->onCancel(fn() => cleanup());
// callback is NOT stored — no memory retained
// registration is pre-disposed

$registration->isDisposed(); // true
$registration->dispose();    // no-op, returns false
```

---

Cancellation is Synchronous
---------------------------

[](#cancellation-is-synchronous)

Like `Promise::cancel()`, cancellation through a `CancellationTokenSource`is **synchronous**. When you call `$cts->cancel()`, all registered `onCancel()` callbacks and all tracked promise cancellations run immediately and in order before `cancel()` returns. This eliminates race conditions where a promise could be resolved in the same tick that cancellation was requested:

```
$cts = new CancellationTokenSource();

$cts->token->onCancel(function () {
    echo "A\n"; // runs first
});

$cts->token->onCancel(function () {
    echo "B\n"; // runs second
});

$cts->cancel();
echo "C\n"; // runs third — after both handlers have already run
```

Because cancellation is synchronous, callbacks registered via `onCancel()`on the token must be **fast**. They run directly on the call stack of `cancel()` — a slow or blocking handler stalls that call stack. Keep handlers to simple cleanup: cancelling a timer, removing a watcher, or closing a handle. For async cleanup, fire and return immediately rather than awaiting:

```
$cts->token->onCancel(function () use ($requestId) {
    // Correct: fire and return
    Loop::addCurlRequest(
        "https://api.example.com/cancel/$requestId",
        [],
        fn() => null
    );

    // Wrong: do not await inside an onCancel handler
    // Http::delete("https://api.example.com/cancel/$requestId")->wait();
});
```

### Exceptions during cancellation

[](#exceptions-during-cancellation)

If any `onCancel()` callback or promise cancellation throws during `cancel()`, the library does not abort mid-loop. All remaining callbacks and promises are still processed. Exceptions are collected and at the end either a single exception is rethrown or — if multiple callbacks threw — an `AggregateErrorException` is thrown containing all of them:

```
$cts->token->onCancel(fn() => throw new \RuntimeException('A failed'));
$cts->token->onCancel(fn() => releaseOtherResource()); // still runs

try {
    $cts->cancel();
} catch (\Hibla\Promise\Exceptions\AggregateErrorException $e) {
    foreach ($e->getErrors() as $error) {
        logger()->error($error->getMessage());
    }
} catch (\Throwable $e) {
    // single exception if only one callback threw
}
```

---

Automatic Timeout
-----------------

[](#automatic-timeout)

Pass a timeout in seconds to the constructor and the source will automatically cancel after that duration. The timeout timer uses `WeakReference` internally — if the source goes out of scope before the timer fires, the timer cancels cleanly without error and does not keep the source alive:

```
// Cancels after 5 seconds
$cts = new CancellationTokenSource(5.0);

$promise = longRunningOperation($cts->token);

$promise->wait(); // throws CancelledException if 5 seconds elapse
```

You can set or reset the timeout dynamically after construction by calling `cancelAfter()`. Each call cancels the previous timer and starts a new one — the constructor timeout is not fixed:

```
$cts = new CancellationTokenSource(5.0); // starts with 5 second timeout
$cts->cancelAfter(10.0);                 // reset — now cancels in 10 seconds
$cts->cancelAfter(2.0);                  // reset again — now cancels in 2 seconds
```

---

Linking Multiple Tokens
-----------------------

[](#linking-multiple-tokens)

`createLinkedTokenSource()` creates a new source that cancels automatically when any of the provided tokens cancel. This is the standard way to combine multiple cancellation signals — user abort, timeout, system shutdown — into a single token you pass into an operation:

```
use Hibla\Cancellation\CancellationTokenSource;

$userCts    = new CancellationTokenSource();     // user clicks cancel
$timeoutCts = new CancellationTokenSource(10.0); // 10 second timeout

// Operation cancels if user cancels OR timeout expires
$linkedCts = CancellationTokenSource::createLinkedTokenSource(
    $userCts->token,
    $timeoutCts->token
);

$result = fetchData($linkedCts->token)->wait();
```

If any of the input tokens are already cancelled at the time `createLinkedTokenSource()` is called, the linked source is cancelled immediately before being returned.

The linked source uses `WeakReference` internally — parent token callbacks do not keep the linked source alive after it goes out of scope. If the linked source is garbage collected before any parent token fires, the link is severed cleanly. Once one parent token fires and cancels the linked source, subsequent parent token firings call `cancel()` on the already-cancelled source — which is a safe no-op.

### Full example — user cancellation, timeout, and `await()`

[](#full-example--user-cancellation-timeout-and-await)

```
use Hibla\Cancellation\CancellationTokenSource;
use function Hibla\async;
use function Hibla\await;

$userCts    = new CancellationTokenSource();     // cancelled when user clicks abort
$timeoutCts = new CancellationTokenSource(30.0); // hard 30 second ceiling

$linkedCts = CancellationTokenSource::createLinkedTokenSource(
    $userCts->token,
    $timeoutCts->token
);

$workflow = async(function () use ($linkedCts) {
    try {
        $user   = await(fetchUser(1), $linkedCts->token);
        $orders = await(fetchOrders($user->id), $linkedCts->token);
        $report = await(generateReport($user, $orders), $linkedCts->token);

        return $report;
    } catch (\Hibla\Promise\Exceptions\CancelledException $e) {
        echo "Workflow cancelled — either user aborted or 30s timeout hit\n";
        return null;
    }
});

// Wire user abort to the source
$abortButton->onClick(fn() => $userCts->cancel());

$result = await($workflow);
```

---

Cleanup Registration with `onCancel()`
--------------------------------------

[](#cleanup-registration-with-oncancel)

`onCancel()` on the token itself is for registering cleanup that is not tied to a specific promise — for example logging, releasing a lock, or updating state when any operation in a group is cancelled.

It returns a `CancellationTokenRegistration` that lets you unregister the callback if the operation completes before cancellation occurs. `dispose()`returns `true` if the callback was successfully removed, `false` if it was already disposed or had already fired:

```
$cts   = new CancellationTokenSource();
$token = $cts->token;

$registration = $token->onCancel(function () use ($tempFile) {
    $tempFile->delete();
});

try {
    $result = doWork($token)->wait();

    // Success — temp file is kept, unregister the cleanup
    $disposed = $registration->dispose(); // true if removed, false if already fired

    return $result;
} catch (\Throwable $e) {
    // Failed — let the cleanup registration remain in case of later cancellation
    throw $e;
}
```

> **Important:** If you let a `CancellationTokenRegistration` go out of scope without calling `dispose()`, the callback remains registered and will still fire when the token is cancelled. The registration's `__destruct()` only nullifies the internal state reference for garbage collection — it does NOT unregister the callback. Always call `dispose()` explicitly if you want to prevent the callback from firing.

If the token is already cancelled when `onCancel()` is called, the callback fires immediately and synchronously, and a pre-disposed registration is returned.

---

Monitoring Tracked Promises
---------------------------

[](#monitoring-tracked-promises)

The token provides methods for inspecting and managing its tracked promises. These are primarily useful for monitoring, diagnostics, and advanced lifecycle management:

```
// How many promises are currently being tracked
$count = $token->getTrackedCount();

// Stop tracking a specific promise without cancelling it —
// useful when an operation completes and you want to detach it
// from the token without affecting its result
$token->untrack($promise);

// Remove all tracked promises without cancelling any of them —
// useful when you want to stop managing a batch of operations
// but let them complete naturally
$token->clearTracked();
```

Note that `clearTracked()` and `untrack()` do not cancel the promises they remove — the promises continue running and will resolve or reject normally. If you need to cancel them, call `cancel()` on the source before clearing.

---

`cancel()` vs `cancelChain()`
-----------------------------

[](#cancel-vs-cancelchain)

The source provides two cancellation methods that mirror the same distinction on `Promise` itself.

**`cancel()`** calls `Promise::cancel()` on all tracked promises — forward propagation only. Cancels the tracked promise and all its children. Does not walk up to the root. Use this when you track root promises directly:

```
$root  = Http::get('/api/users');  // onCancel() -> abort HTTP
$child = $root->then(fn($r) => json_decode($r->getBody()));

// Track the root — cancel() is sufficient
$cts->token->track($root);
$cts->cancel();
// $root->onCancel() fires -> HTTP aborted
// $child cancelled via forward propagation
```

**`cancelChain()`** calls `Promise::cancelChain()` on all tracked promises — walks up to the root before cancelling downward. Use this when you only hold child promise references but need root-level `onCancel()` handlers to fire for proper resource cleanup:

```
$root  = Http::get('/api/users');  // onCancel() -> abort HTTP
$child = $root->then(fn($r) => json_decode($r->getBody()));

// Only hold child reference — cancelChain() walks up to find root
$cts->token->track($child);
$cts->cancelChain();
// walks up to $root
// $root->onCancel() fires -> HTTP aborted
// $child cancelled via forward propagation
```

> **Important:** Only use `cancelChain()` when you own the full promise chain and no external code holds references to ancestor promises. It walks up to the root and cancels everything from there. If the root promise is shared with other callers, `cancelChain()` will cancel their operations too — this is almost certainly a bug. When in doubt, track root promises directly and use `cancel()`.

---

Integration with `await()`
--------------------------

[](#integration-with-await)

`await()` from `hiblaphp/async` accepts an optional `CancellationToken` as its second argument. When provided, it automatically calls `token->track($promise)` — you do not need to call `track()` manually at the `await()` call site:

```
use function Hibla\await;

async(function () use ($token) {
    // Token automatically tracks the promise — no manual track() needed
    $user   = await(fetchUser(1), $token);
    $orders = await(fetchOrders($user->id), $token);

    return compact('user', 'orders');
});
```

If you are not using `hiblaphp/async`, call `track()` on the token directly before awaiting the promise:

```
// Without hiblaphp/async — use track() manually
$promise = fetchUser(1);
$token->track($promise);
$user = $promise->wait();
```

---

Resource Cleanup on Scope Exit
------------------------------

[](#resource-cleanup-on-scope-exit)

`CancellationTokenSource` implements `__destruct()` which cancels the timeout timer and clears all callbacks and tracked promises when the source goes out of scope.

> **Important:** `__destruct()` does NOT call `cancel()` — it only clears the internal state. Both tracked promises and registered `onCancel()`callbacks are silently dropped without firing. Promises continue running and will resolve or reject normally. If you need promises to be cancelled and `onCancel()` handlers to fire when the source goes out of scope, call `cancel()` explicitly in a `finally` block:

```
function doWork(): mixed
{
    $cts = new CancellationTokenSource(30.0);

    $cts->token->onCancel(function () {
        echo "cancelled\n"; // will NOT fire unless cancel() is called explicitly
    });

    try {
        return longOperation($cts->token)->wait();
    } finally {
        // Explicitly cancel to ensure tracked promises are cancelled
        // and onCancel() handlers fire before $cts is destroyed.
        // Without this, both are silently dropped on scope exit.
        if (! $cts->token->isCancelled()) {
            $cts->cancel();
        }
    }
}
```

The timeout timer is the exception — it is always cancelled automatically on scope exit regardless of whether `cancel()` is called explicitly:

```
On scope exit ($cts goes out of scope):

  Timeout timer        → always cancelled cleanly ✓
  onCancel() callbacks → silently dropped, NOT fired ✗ unless cancel() called
  tracked promises     → silently untracked, NOT cancelled ✗ unless cancel() called

```

---

API Reference
-------------

[](#api-reference)

### `CancellationTokenSource`

[](#cancellationtokensource)

MethodDescription`__construct(?float $timeoutSeconds)`Create a source. Pass a timeout in seconds for automatic cancellation.`cancel()`Cancel synchronously. Calls `Promise::cancel()` (forward-only) on all tracked promises. Idempotent. Collects and throws `AggregateErrorException` if multiple callbacks throw.`cancelChain()`Cancel synchronously. Calls `Promise::cancelChain()` (walks to root) on all tracked promises. Only use when you own the full chain.`cancelAfter(float $seconds)`Set or reset the automatic cancellation timer. Resets on each call.`createLinkedTokenSource(CancellationToken ...$tokens)`Static. Returns a new source that cancels when any input token cancels.`$token`Readonly public property. The `CancellationToken` associated with this source.### `CancellationToken`

[](#cancellationtoken)

MethodDescription`isCancelled(): bool`Non-throwing check. True if the source has been cancelled.`throwIfCancelled(): void`Throws `CancelledException` if cancelled. Preferred for long-running work — unwinds through finally blocks.`onCancel(callable $callback): CancellationTokenRegistration`Register a synchronous cleanup callback. Must be fast — no blocking or awaiting. Returns a registration for unregistering. If already cancelled, fires immediately and returns a pre-disposed registration. No-op on `none()` — returns a pre-disposed registration without storing the callback.`track(PromiseInterface $promise): PromiseInterface`Register a promise for automatic cancellation. Auto-untracked when promise settles. Safe no-op on already-settled promises and on `none()`. Returns the same promise.`untrack(PromiseInterface $promise): void`Stop tracking a promise without cancelling it.`getTrackedCount(): int`Returns the number of currently tracked promises.`clearTracked(): void`Remove all tracked promises without cancelling them.`CancellationToken::none()`Static singleton. A token that can never be cancelled. All methods are safe to call — `onCancel()` and `track()` are no-ops that store nothing against the singleton.### `CancellationTokenRegistration`

[](#cancellationtokenregistration)

MethodDescription`dispose(): bool`Unregister the callback. Returns `true` if removed, `false` if already disposed or already fired. Safe to call multiple times. Does NOT fire automatically on garbage collection — call explicitly to prevent the callback from firing.`isDisposed(): bool`True if `dispose()` was called.---

Development
-----------

[](#development)

```
git clone https://github.com/hiblaphp/cancellation.git
cd cancellation
composer install
```

```
./vendor/bin/pest
```

```
./vendor/bin/phpstan analyse
```

---

Credits
-------

[](#credits)

- **API Design:** Inspired by .NET's `CancellationToken` and `CancellationTokenSource` pattern.
- **Promise Integration:** Built on [hiblaphp/promise](https://github.com/hiblaphp/promise).
- **Event Loop:** Powered by [hiblaphp/event-loop](https://github.com/hiblaphp/event-loop).

---

License
-------

[](#license)

MIT License. See [LICENSE](./LICENSE) for more information.

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance63

Regular maintenance activity

Popularity19

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/adefeba01f7aab9f1eed1cc3f5015be296d86126a4adb50257d81ec0bf23c8ca?d=identicon)[RCalicdan](/maintainers/RCalicdan)

---

Top Contributors

[![rcalicdan](https://avatars.githubusercontent.com/u/163510169?v=4)](https://github.com/rcalicdan "rcalicdan (10 commits)")

### Embed Badge

![Health badge](/badges/hiblaphp-cancellation/health.svg)

```
[![Health](https://phpackages.com/badges/hiblaphp-cancellation/health.svg)](https://phpackages.com/packages/hiblaphp-cancellation)
```

###  Alternatives

[razonyang/yii2-rate-limiter

Yii2 Rate Limiter

1025.0k1](/packages/razonyang-yii2-rate-limiter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
