flyokai / revolt-event-loop
Rock-solid event loop for concurrent PHP applications.
Requires
- php: >=8.1
Requires (Dev)
- ext-json: *
- jetbrains/phpstorm-stubs: ^2019.3
- phpunit/phpunit: ^9
- psalm/phar: ^5.15
Replaces
This package is auto-updated.
Last update: 2026-04-28 12:34:05 UTC
README
User docs →
README.md· Agent quick-ref →CLAUDE.md· Agent deep dive →AGENTS.md
Rock-solid cooperative event loop for PHP 8.1+ — fork of
revolt/event-loopused as the foundation of every async operation in the Flyokai framework.
This package replaces revolt/event-loop (via Composer's replace) with a Flyokai-tracked fork that adds EventLoop::onMysqli() for the async-mysqli driver. The static API and semantics are otherwise identical to upstream.
Heads up. Most users want upstream
revolt/event-loop. This package only matters if you depend on Flyokai'sflyokai/laminas-db-driver-async, which usesonMysqli().
Features
- Static
EventLoopaccessor —defer,delay,repeat,onReadable,onWritable,onSignal,onMysqli SuspensionAPI — fiber pause/resume viagetSuspension()->suspend()/resume()/throw()FiberLocal— per-fiber storage backed byWeakMap- Auto-selected driver —
UvDriver>EvDriver>EventDriver>StreamSelectDriver onMysqli()— Flyokai-specific addition: register a callback that fires when amysqliconnection running an async query is ready to be reaped
Installation
composer require revolt/event-loop
flyokai/revolt-event-loop replaces revolt/event-loop, so the standard package name resolves to this fork inside Flyokai installs.
Quick start
use Revolt\EventLoop; EventLoop::defer(function () { echo "next tick\n"; }); EventLoop::delay(0.5, function () { echo "after 500ms\n"; }); $id = EventLoop::repeat(1.0, function () { echo "tick\n"; }); EventLoop::run(); // call from {main} only
Suspension from inside a fiber
$suspension = EventLoop::getSuspension(); EventLoop::delay(0.1, fn() => $suspension->resume('done')); $result = $suspension->suspend(); // 'done'
Async mysqli (the Flyokai addition)
$mysqli->query($sql, MYSQLI_ASYNC); $suspension = EventLoop::getSuspension(); EventLoop::onMysqli($mysqli, function ($watcherId, mysqli $link) use ($suspension) { EventLoop::cancel($watcherId); $suspension->resume($link->reap_async_query()); }); $result = $suspension->suspend();
This is the primitive that flyokai/laminas-db-driver-async's AsyncMysqli strategy is built on.
Driver selection
Auto-selected by DriverFactory in priority order:
UvDriver—ext-uv(libuv). Highest performance.EvDriver—ext-ev(libev).EventDriver—ext-event(libevent). Cross-platform.StreamSelectDriver— pure PHPstream_select(). Default fallback. Limited to ~1024 FDs.TracingDriver— debug wrapper, setREVOLT_DRIVER_DEBUG_TRACE=1.
Override with REVOLT_DRIVER=\Full\Class\Name.
Tick model
Activate queued callbacks
→ Execute defer callbacks
→ Dispatch one timer / signal / stream callback (each)
→ Continue while any referenced callbacks remain
Every callback runs in its own fiber. Callbacks must return null or void — anything else throws InvalidCallbackError.
Gotchas
- PHP version: requires
>=8.1.17or>=8.2.4(older versions have GC bugs). Override withREVOLT_DRIVER_SUPPRESS_ISSUE_10496=1. - Callback return: must be
null/void. run()is{main}only. From a fiber, use theSuspensionAPI.fclose()doesn't auto-cancel —cancel()the callback explicitly.- Signals are process-global. Same signal across drivers is undefined. Requires
ext-pcntl. StreamSelectDriverFD ceiling — ~1024. Installext-uv/ext-ev/ext-eventto raise it.- WeakMap fiber refs — circular references in user code can collect a fiber unexpectedly during GC.
- Dead
{main}suspension — if the loop exits without resuming{main}, the suspension is permanently invalid. - Error handler must handle or re-throw — exceptions inside the error handler halt the loop immediately.
License
MIT — Copyright (c) 2021- Revolt (Aaron Piotrowski, Cees-Jan Kiewiet, Christian Lück, Niklas Keller, and contributors). See LICENSE.
See also
flyokai/laminas-db-driver-async—AsyncMysqliconsumer ofonMysqli()- Upstream: https://revolt.run — for non-Flyokai projects, use
revolt/event-loopdirectly.