True Async is coroutines for PHP as an ordinary PHP extension, the module true_async. It
rebuilds TrueAsync to load into a PHP built from stock
php-src plus the async RFCs, with no php-src patches of its own.
Status: early. The extension builds, loads and is tested in CI on Linux and Windows; coroutines come next. The work plan is
dev/PLAN.md.
- Goals
- Architecture
- Roadmap
- The core
- Known limitations
- Build
- Tests
- Repository layout
- Support the project
- License
-
No private core. Today's TrueAsync needs its own fork of php-src, so it cannot be installed into a distribution's PHP or through PIE. True Async uses only the public core APIs proposed upstream:
- the scheduler API, php/php-src#22561;
- the IO APIs: IO hooks, the Poll API additions and the Ring, php/php-src#23997.
A change it needs in the core goes to the RFC as a request, never into a patch shipped here.
-
The TrueAsync API. The classes, functions and behaviour of TrueAsync are kept: code and tests written for it run here. Where an RFC sets a stricter rule, the RFC wins, and the difference is recorded in
dev/DECISIONS.md. -
The reactor on the core's IO APIs. Waiting, timers and IO go through the core's
php_io_queue: the Ring over ior where built, the Poll queue otherwise. No libuv. -
Performance. Spawn, switch, suspend and await are measured against TrueAsync; a change on the hot path comes with its numbers.
-
Tests first. Each stage freezes its list of tests, ported from TrueAsync, before its code. Every run goes through debug and ASAN+UBSAN builds, and mutation testing checks that the tests catch broken code.
-
Linux and Windows. Every stage builds and runs its tests on both.
flowchart TB
app["PHP code<br/>Async\spawn, await, suspend, Coroutine"]
subgraph ext["true_async extension"]
sched["Scheduler<br/>coroutines, run queue, cancellation"]
reactor["Reactor<br/>timers, idle wait"]
provider["IO provider<br/>blocking PHP functions suspend the coroutine"]
collector["Async object collector<br/>deadlocks, unreachable waits"]
end
subgraph core["php-src core with the RFCs"]
sapi["Scheduler API<br/>21 slots, #22561"]
hooks["IO hooks<br/>#23997"]
queue["php_io_queue<br/>Ring (ior) or Poll"]
end
app --> sched
sched -- registers --> sapi
sched --> reactor
reactor --> queue
provider -- implements --> hooks
provider --> queue
collector -. walks the wait graph .-> sched
| Stage | Progress | Done |
|---|---|---|
✓ S1 · Core branch async-core-io |
██████████ |
100 % |
| ✓ S2 · Repository and test system | ██████████ |
100 % |
| ✓ S3 · Scheduler on the scheduler API | ██████████ |
100 % |
| ✓ S4 · Reactor on Poll, Poll additions and Ring | ██████████ |
100 % |
| ✓ S5 · Futures, timeouts and combinators | ██████████ |
100 % |
| ✓ S6 · IO hooks provider | ██████████ |
100 % |
| ✓ S7 · Async object collector | ██████████ |
100 % |
| ▶ S8 · Review checks and RFC change list | █████████░ |
88 % |
| S9 · Higher layers, one at a time | █████████░ |
93 % |
| ✓ S10 · Beyond the RFCs | ██████████ |
100 % |
S11 · Namespace Async renamed to TrueAsync |
░░░░░░░░░░ |
0 % |
| S13 · The provider from the request's start, after bukka's pipe refactor | ░░░░░░░░░░ |
0 % |
| ✓ S14 · Health check fixes | ██████████ |
100 % |
| S15 · Test layers of S2 not built yet | ░░░░░░░░░░ |
0 % |
Total: 77 %, the mean over 14 stages; a stage counts its closed steps in dev/PLAN.md. Generated by tools/roadmap.py.
Until the RFCs are merged, the core is the php-src branch
async-core-io: php-src master with the
two proofs of concept merged, nothing else. dev/WORKFLOW.md pins its revision
and gives its configure line.
Regular files are read and written on the thread. A fread() or fwrite() on a file holds
every coroutine until it returns; a socket read does not. The core's Ring can run file
IO in the background, but while a coroutine waits on a stream the core refuses that stream to every
other coroutine with Error: Concurrent access to a stream. With files in the background, two
coroutines writing one log file, or including from one phar, would fail; TrueAsync queues them on
the handle instead. Files go to the background once the core queues such waits
(dev/RFC-CHANGES.md 12). The same Error already meets two coroutines using
one socket or pipe at once.
Linux, against an installed core:
phpize
./configure --with-php-config=<core prefix>/bin/php-config --enable-true-async
make
php -d extension=modules/true_async.so -d true_async.enable=1 --ri true_asynctrue_async.enable (system INI, default 0) lets a PHP that loads the extension run another
scheduler.
Windows: the repository goes into ext\true_async of the core tree and PHP is built with
--enable-true-async; tools/windows/ does it with Debug_TS or Release_TS.
C sources follow .clang-format (the php-async configuration plus PHP macro rules):
tools/format.sh rewrites them, tools/format.sh --check is what CI runs.
TRUE_ASYNC_CORE_SRC=<core checkout> tools/test.py --lane pocs-dbg # pocs-asan, pocs-dbg-cov
tools/check-lists.py --reference <php-async checkout>
tools/mull.py --known-answer| Lane | Build |
|---|---|
pocs-dbg |
ZTS debug core, ZendMM on |
pocs-asan |
ZTS debug core with ASAN + UBSAN, USE_ZEND_ALLOC=0 |
pocs-dbg-cov |
the debug lane with gcov; line coverage of src/ |
pocs-dbg-mull |
clang 18 with Mull, for mutation testing (tools/mull.py) |
pocs-win |
Windows, the extension built inside the core tree |
The test lists, the lanes and the CI are described in dev/plans/S2.md.
config.m4, config.w32, php_true_async.h build and module entry
src/ extension sources
tests/ tests by group; tests/lists/ holds the frozen lists
tools/ runner, list checker, mutation and CI scripts
dev/ plan, principles, decisions, workflow
If you find TrueAsync useful, you can support its development:
BSD-3-Clause, see LICENSE.