puff / async
Puff async fiber runtime with tasks, futures, timers, channels, wait groups, Fiber-local context, deferred callbacks, and non-blocking stream watchers.
Requires
- php: ^8.2
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-08-10 06:23:30 UTC
README
Pure PHP 8.2+ Fiber runtime with tasks, futures, timers, channels, wait groups, Fiber-local context, deferred callbacks, and non-blocking stream watchers.
composer require puff/async
Run concurrent tasks
puff_run() starts a root Fiber. puff_async() schedules child tasks, puff_delay() suspends only the current Fiber, and puff_await() returns a task result or rethrows its exception.
$result = puff_run(function (): array { $first = puff_async(static function (): string { puff_delay(0.05); return 'first'; }); $second = puff_async(static function (): string { puff_delay(0.03); return 'second'; }); return [puff_await($first), puff_await($second)]; });
Tasks expose their Future for completion callbacks:
$task = puff_async(static fn (): int => 42); $task->future()->onComplete( static function (?Throwable $error, mixed $value): void { // Inspect the result without blocking. }, );
Deferred callbacks
puff_defer() runs cleanup callbacks in last-in, first-out order when the current Fiber finishes, including when it throws.
puff_run(function (): void { puff_defer(static fn () => fclose($first)); puff_defer(static fn () => fclose($second)); // $second closes before $first. });
Channels
An unbuffered channel synchronizes a producer and consumer. Pass a positive capacity to create a buffered channel.
$message = puff_run(function (): mixed { $channel = puff_channel(); puff_async(static function () use ($channel): void { puff_delay(0.01); $channel->push('hello'); }); return $channel->pop(1.0); });
push() and pop() return false after closure or timeout where applicable. Use close(), isClosed(), length(), and capacity() to inspect channel state.
Wait groups
A wait group blocks the current Fiber until every registered worker calls done() or the optional timeout expires.
$completed = puff_run(function (): array { $group = puff_wait_group(3); $completed = []; foreach ([30, 10, 20] as $delayMs) { puff_async(static function () use ($group, &$completed, $delayMs): void { puff_defer(static fn () => $group->done()); puff_delay($delayMs / 1_000); $completed[] = $delayMs; }); } $group->wait(1.0); return $completed; });
Fiber context and identity
Context values are isolated per Fiber and cleared automatically. Runtime helpers expose the current task ID, parent ID, and pending task count.
puff_id()returns the current runtime-managed Task ID, or-1outside a managed Task.puff_parent_id()returns the parent Task ID,-1for the root Task, orfalseoutside a Fiber.
The parent ID links a child Task to the Task that created it:
$identity = puff_run(function (): array { $rootId = puff_id(); $child = puff_async(static fn (): array => [ 'id' => puff_id(), 'parent_id' => puff_parent_id(), ]); return [ 'root_id' => $rootId, 'child' => puff_await($child), ]; });
Fiber-local context can be combined with the identity helpers:
use Puff\Async\Context; $metadata = puff_run(function (): array { Context::set('request-id', 'req-123'); return [ 'request_id' => Context::get('request-id'), 'fiber_id' => puff_id(), 'parent_id' => puff_parent_id(), 'task_count' => puff_task_count(), ]; });
Use Runtime::onCleanup() to register additional cleanup listeners.
Event loop
The shared event loop supports queued callbacks, one-shot timers, and readable or writable stream watchers.
use Puff\Async\EventLoop; $loop = EventLoop::get(); $timer = $loop->delay(0.1, static fn () => print "timer fired\n"); $watcher = $loop->onReadable($stream, static function ($stream) use ($loop): void { echo fread($stream, 8192); $loop->stop(); }); $loop->run(); $loop->cancel($timer); $loop->cancel($watcher);
runUntil() also works on the main PHP stack. Once its condition becomes true, the loop returns before waiting on unrelated later timers.
Short aliases
The package provides equivalent concise helpers:
| Full helper | Alias |
|---|---|
puff_run() |
run() |
puff_async() |
co(), go() |
puff_await() |
await() |
puff_delay() |
delay() |
puff_defer() |
defer() |
puff_channel() |
channel(), chan() |
puff_wait_group() |
WaitGroup(), wg() |