Conformance
Every adapter runs the same 22 examples from the api and must print identical output. Results are produced by CI from the current main of every repository, daily and on every push.
Summary
| Runtime | Library / extension version tested | PHP | Result | Scope state | Stress | Run at |
|---|---|---|---|---|---|---|
| AMPHP | adapter 9a8126c, amphp/amp v3.1.3, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | full | 6/6 | 2026-10-13 23:05 UTC |
| OpenSwoole (swoole adapter) | adapter 7395594, ext-openswoole 25.2.0, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | api-driven | 6/6 | 2026-10-13 23:07 UTC |
| phasync | adapter 6f795cd, phasync/phasync 2.0.0-beta7, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | full | 6/6 | 2026-10-13 23:05 UTC |
| ReactPHP | adapter 83b4fa8, react/async v4.3.0, react/event-loop v1.6.0, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | api-driven | 5/6 | 2026-10-13 23:06 UTC |
| Revolt | adapter 7f86ebe, revolt/event-loop v1.0.9, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | full | 6/6 | 2026-10-13 23:05 UTC |
| Swoole | adapter 7395594, ext-swoole 6.2.3, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | api-driven | 6/6 | 2026-10-13 23:06 UTC |
| reference adapter (api test double) | adapter 13cdbd0, php 8.4.26, api 13cdbd0 | 8.4.26 | all 22 pass | not run | 6/6 | 2026-10-13 23:05 UTC |
Scope probes
Three standalone scripts per adapter show how far per-scope state (including superglobals) follows the code. api proves that code driven through the fiber-interop api keeps its scope; loop proves the same for code using the loop's own primitives; raw fiber proves that a plain Fiber keeps the scope of its creator.
A userland polyfill sees only the switches that pass through a hook it controls: everything under Revolt and AMPHP, callbacks but not await() under ReactPHP, nothing Swoole performs itself. Covering every switch needs the runtime: the engine for fibers (the case made in the the Scope RFC (draft)), and for Swoole the two switch hooks its core already has but does not expose to PHP. The ext-scope columns run the same probes with the native extension loaded. The extension provides the identity; per-scope superglobals are the RFC's secondary vote and are not in the extension yet, so api and loop fail there today and raw fiber passes. That is the measurement, not a defect of the extension.
| Runtime | api | loop | raw fiber | |||
|---|---|---|---|---|---|---|
| polyfill | ext-scope | polyfill | ext-scope | polyfill | ext-scope | |
| AMPHP | ok | FAIL | ok | FAIL | ok | ok |
| OpenSwoole (swoole adapter) | ok | FAIL | FAIL | FAIL | ok | ok |
| phasync | ok | FAIL | ok | FAIL | ok | ok |
| ReactPHP | ok | FAIL | FAIL | FAIL | ok | ok |
| Revolt | ok | FAIL | ok | FAIL | ok | ok |
| Swoole | ok | FAIL | FAIL | FAIL | ok | ok |
| reference adapter (api test double) | not run | not run | not run | not run | not run | not run |
Exceptions
- ReactPHP:
stress testForeignFiberChildIsNeverOrphaned-seed=88546215 (FIBER_INTEROP_STRESS_SEED=88546215, iteration 0): LogicException: fiber-interop/react: cannot wait in place from a fiber nobody owns while the React loop is running; create the fiber wi - AMPHP:
scope probe api (ext-scope)-a after sleep 0 saw {"req":"b"} - AMPHP:
scope probe loop (ext-scope)-a after delay 0 saw {"req":"b"} - OpenSwoole (swoole adapter):
scope probe api (ext-scope)-after run() $_GET is {"req":"b"} - OpenSwoole (swoole adapter):
scope probe loop (polyfill)-a after native sleep 0 saw [] - OpenSwoole (swoole adapter):
scope probe loop (ext-scope)-a after native sleep 0 saw {"req":"b"} - phasync:
scope probe api (ext-scope)-a after sleep 0 saw {"req":"b"} - phasync:
scope probe loop (ext-scope)-a after sleep 0 saw {"req":"b"} - ReactPHP:
scope probe api (ext-scope)-after run() $_GET is {"req":"b"}, expected root [] - ReactPHP:
scope probe loop (polyfill)-a after sleep 1 saw null - ReactPHP:
scope probe loop (ext-scope)-a after sleep 1 saw null - Revolt:
scope probe api (ext-scope)-scope a after sleep 0 saw {"v":"b"} - Revolt:
scope probe loop (ext-scope)-scope a step 0 saw {"v":"b"} - Swoole:
scope probe api (ext-scope)-after run() $_GET is {"req":"b"} - Swoole:
scope probe loop (polyfill)-a after native sleep 0 saw [] - Swoole:
scope probe loop (ext-scope)-a after native sleep 0 saw {"req":"b"}
Full matrix
Show the full matrix (examples by runtimes)
| Example | AMPHP | OpenSwoole (swoole adapter) | phasync | ReactPHP | Revolt | Swoole | reference |
|---|---|---|---|---|---|---|---|
01-go-awaitgo() and await() return a task result | ok | ok | ok | ok | ok | ok | ok |
02-outside-run-throwsgo/await/sleep throw outside run(); run() works | ok | ok | ok | ok | ok | ok | ok |
03-sleep-interleavessleep() interleaves tasks by duration | ok | ok | ok | ok | ok | ok | ok |
04-timeout-leaves-task-runningawait timeout throws; the task keeps running | ok | ok | ok | ok | ok | ok | ok |
05-late-result-ignoreda result arriving after a timeout is ignored | ok | ok | ok | ok | ok | ok | ok |
06-exception-rethrown-unchangedtask exceptions are rethrown unchanged | ok | ok | ok | ok | ok | ok | ok |
07-rejected-non-throwablenon-Throwable rejection becomes RejectedException | ok | ok | ok | ok | ok | ok | ok |
08-await-thenableany then(ok, fail) object is awaitable | ok | ok | ok | ok | ok | ok | ok |
09-await-allawait_all returns results under the same keys | ok | ok | ok | ok | ok | ok | ok |
10-await-anyawait_any returns the first fulfilled result | ok | ok | ok | ok | ok | ok | ok |
11-readable-concurrentseveral fibers wait readable() on one stream | ok | ok | ok | ok | ok | ok | ok |
12-writablewritable() waits and returns the resource | ok | ok | ok | ok | ok | ok | ok |
13-scope-inherited-by-gogo() tasks inherit the starter's Scope | ok | ok | ok | ok | ok | ok | ok |
14-scope-in-queued-callbackqueued callbacks resume tasks in their own Scope | ok | ok | ok | ok | ok | ok | ok |
15-in-place-foreign-fiberforeign fibers wait in place inside run() | ok | ok | ok | ok | ok | ok | ok |
16-unhandled-exception-reaches-handlerunhandled service errors reach the error handler | ok | ok | ok | ok | ok | ok | ok |
17-await-is-repeatablean awaitable can be awaited repeatedly | ok | ok | ok | ok | ok | ok | ok |
18-run-is-structuredrun() waits for every task started inside it | ok | ok | ok | ok | ok | ok | ok |
19-nested-runnested run() waits only for its own tasks | ok | ok | ok | ok | ok | ok | ok |
20-service-does-not-block-runa service() task does not delay run() | ok | ok | ok | ok | ok | ok | ok |
21-superglobals-per-scope$_GET is per scope; each scope sees its own | ok | ok | ok | ok | ok | ok | ok |
22-readiness-on-closereadable()/writable() return on peer close and on local close: readiness means the next operation will not block | ok | ok | ok | ok | ok | ok | ok |
| Passing | 22/22 | 22/22 | 22/22 | 22/22 | 22/22 | 22/22 | 22/22 |
See the specification for what the conformance set does and does not prove.