bagart / async-kernel
Async kernel for PHP — Fibers, Promises, cooperative scheduling
Requires
- php: ^8.5
- monolog/monolog: ^3.0
- psr/log: ^3.0
- psr/simple-cache: *
This package is not auto-updated.
Last update: 2026-07-23 16:57:01 UTC
README
A lightweight async runtime for PHP 8.1+ built on Fibers and Promises.
Overview
Async Kernel provides a single-threaded event loop that coordinates concurrent tasks using PHP Fibers, Guzzle promises, and a cooperative scheduling model. It is designed for long-running daemons that need to perform non-blocking I/O (HTTP requests, polling, scheduled tasks) without external dependencies like ReactPHP or Amp.
Architecture
AsyncKernel
├── Drivers (AsyncTickableContract) — drive async I/O (Guzzle queue, curl_multi)
├── Daemons (DaemonContract) — long-running tasks polled each tick
│ └── FiberScheduler (SchedulerContract) — Fiber-based cooperative scheduler
└── Promises (PromiseContract) — resolve/reject with then/otherwise chains
Core Components
| Class | Purpose |
|---|---|
AsyncKernel |
Main event loop — ticks drivers then daemons, manages idle/busy sleep |
ASKPromise / ASKDeferred |
Promise/A+ style primitives with then, otherwise, wait, cancel |
ASKPromiseResolver |
Awaits a promise from within a Fiber or blocking context |
ASKFiberScheduler |
Enqueues and schedules Fibers with sleep(delay) support |
GuzzlePromiseAdapter |
Wraps GuzzleHttp\Promise\PromiseInterface into ASKPromiseContract |
GuzzleTickableDriver |
Ticks GuzzleHttp\Promise\Utils::queue() each cycle |
CurlMultiClient |
Ticks a CurlMultiHandler each cycle |
Requirements
- PHP 8.1+
guzzlehttp/guzzle(for Guzzle drivers/adapters)
Quick Start
use BAGArt\AsyncKernel\AsyncKernel;use BAGArt\AsyncKernel\Network\Drivers\GuzzleTickableDriver;use BAGArt\AsyncKernel\Promise\GuzzlePromiseAdapter;use BAGArt\AsyncKernel\Promise\ASKPromiseResolver;use GuzzleHttp\Client;use Monolog\Handler\StreamHandler;use Monolog\Logger; $logger = new Logger('app'); $logger->pushHandler(new StreamHandler('php://stderr', Logger::INFO)); $kernel = new AsyncKernel($logger); $kernel->addTickable(new GuzzleTickableDriver()); $client = new Client(['timeout' => 10]); $resolver = new ASKPromiseResolver($kernel); $promise = GuzzlePromiseAdapter::wrap( $client->requestAsync('GET', 'https://example.com/api') ); $result = $resolver->await($promise);
Creating a Daemon
Implement ASKDaemonContract to define a long-running task:
use BAGArt\AsyncKernel\Contracts\Daemons\ASKDaemonContract;use BAGArt\AsyncKernel\Contracts\ASKPromiseContract; final class MyDaemon implements ASKDaemonContract { private ?ASKPromiseContract $pending = null; private int $nextRunAfter = 0; public function startup(): void { } public function onError(\Throwable $e): void { } public function name(): string { return substr(strrchr(static::class, '\\'), 1); } public function tick(): void { if ($this->pending !== null) { // process previous result if ($this->pending->getState() === ASKPromiseContract::FULFILLED) { $data = $this->pending->getValue(); // ... } $this->pending = null; $this->nextRunAfter = time() + 30; return; } if (time() < $this->nextRunAfter) { return; } // start new async work $this->pending = $this->fetchData(); } public function shutdown(): bool { return $this->pending === null; } public function isIdle(): bool { return $this->pending === null; } public function queueSize(): int { return $this->pending !== null ? 1 : 0; } }
Register it with the kernel:
$kernel->addDaemon(new MyDaemon()); $kernel->run();
Fiber Scheduler
Use ASKFiberScheduler to run concurrent Fiber-based tasks:
use BAGArt\AsyncKernel\Drivers\ASKFiberScheduler; $scheduler = new ASKFiberScheduler(); $scheduler->enqueue(function (): void { echo "Task 1 started\n"; Fiber::suspend(); // yield echo "Task 1 resumed\n"; }); $scheduler->enqueue(function (): void { echo "Task 2 started\n"; }); $kernel->addDaemon($scheduler); $kernel->run();
Delayed Execution
$scheduler->sleep(function (): void { echo "Delayed task\n"; }, seconds: 5);
Promise States
PENDING → FULFILLED (resolve)
↘ REJECTED (reject)
↘ cancelled (cancel)
then($onFulfilled, $onRejected)— chain handlersotherwise($onRejected)— catch rejectionwait($unwrap)— block until settled (requires a wait function or strategy)cancel()— mark as cancelled
PromiseResolver
PromiseResolver::await($promise, $timeout) works in two modes:
- Inside a Fiber — suspends the Fiber, resumes on resolve/reject
- Outside a Fiber — spins
kernel->tick()until settled
Configuration
use BAGArt\AsyncKernel\ASKClock;use BAGArt\AsyncKernel\Enum\ExceptionPolicy;use BAGArt\AsyncKernel\KernelSleepStrategy\AdaptiveKernelSleepStrategy; $kernel = new AsyncKernel( logger: $kernelLogger, clock: new ASKClock(), sleepStrategy: new AdaptiveKernelSleepStrategy( idleMicroseconds: 1_000, // sleep when queue empty busyMicroseconds: 0, // sleep between busy ticks ), exceptionPolicy: ExceptionPolicy::INTERRUPT, // throw on daemon/driver exceptions );
Sleep Strategies
| Strategy | Description |
|---|---|
AdaptiveKernelSleepStrategy |
Linearly increases idle sleep up to 1ms; configurable idle/busy microseconds |
ExponentialBackoffSleepStrategy |
Doubles idle sleep each tick (capped) |
NoSleepStrategy |
No sleep between ticks (busy-wait) |
Exception Policies
| Policy | Behavior |
|---|---|
ExceptionPolicy::IGNORE |
Log and continue |
ExceptionPolicy::STOP_KERNEL |
Gracefully stop the kernel |
ExceptionPolicy::RESTART_DAEMON |
Log and continue (future: restart daemon) |
ExceptionPolicy::INTERRUPT |
Rethrow the exception (default) |
Graceful Shutdown
$kernel->shutdown() calls shutdown() on each daemon in a loop until all return true. Daemons should flush pending
work and return true when fully stopped.
$kernel->stop($reason) requests a stop after the current tick completes.
Examples
Run the example daemon (fetches USD→EUR rate every 10s):
php commands/example-daemon.php php commands/example-daemon.php --interval=5
Directory Structure
src/
├── Contracts/
│ ├── AsyncKernelContract.php — tick/run interface
│ ├── AsyncTickableContract.php — tick/isIdle/queueSize
│ ├── DaemonContract.php — daemon with shutdown
│ ├── PromiseContract.php — promise states & chaining
│ └── SchedulerContract.php — fiber scheduler interface
├── Exceptions/
│ └── AsyncKernelException.php
├── Runtime/
│ ├── ClientParts/
│ │ ├── AsyncSocketKernel.php
│ │ ├── CurlMultiDriver.php
│ │ ├── GuzzleDriver.php
│ │ ├── GuzzlePromiseAdapter.php
│ │ └── OutboundRequest.php
│ ├── AsyncKernel.php
│ ├── Deferred.php
│ ├── FiberScheduler.php
│ ├── Promise.php
│ ├── PromiseFactory.php
│ └── PromiseResolver.php