Releases: true-async/php-async
v0.7.2
What's changed
Thread transfer — closure scope fix
- fix(#161): closures transferred via
spawn_thread/ThreadPool/ThreadChannelnow carry their class scope — astaticclosure declared inside a class arrived in the worker without a scope, soself::/static::threwCannot access "self" when no class scope is active; a$this-bound closure resolvedZ_OBJCE($this)instead of its declaring class, breakingself::and private-member visibility under inheritance. The snapshot now carriesscope/called_scopeby name and re-resolves them in the target thread. A missing class throwsCannot restore closure scope: class "X" not found in the target thread. Closures scoped to anonymous classes are rejected at transfer time.
ext/curl — libcurl 8.20.x DNS hang fix
- fix(curl): async DNS hang on libcurl 8.20.x — libcurl 8.20.0 moved to a thread-pool DNS resolver whose completion socketpair is not surfaced via
CURLMOPT_SOCKETFUNCTION, so the async curl integration (which drives the multi handle purely throughcurl_multi_socket_action()) never received the resolve result —running_handlesstayed pinned and DNS timed out withCURLE_OPERATION_TIMEDOUT. Fixed by callingcurl_multi_perform()in both timer callbacks to drain resolver results. The workaround is#if-scoped to>= 8.20.0 && < 8.21.0only — it compiles out on 8.19 and earlier (native socketpair path) and on 8.21+, where libcurl fixes this upstream (curl/curl#21476).
Requires
- php-src
php-8.6.0-trueasync-0.7.2(libcurl DNS fix inext/curl)
v0.7.1
What's changed
ThreadPool — memory safety & correctness fixes
- fix(threadpool): per-task nursery scope for sync tasks — snapshot UAF fixed: sync task body now runs in its own nursery
Scope; spawned coroutines are drained before the snapshot arena is freed (Windows debug-heap crash / ASAN-caught on Linux) - fix(threadpool): deliver fatal in sync task body to awaiter — a fatal (OOM/exit) no longer hangs the awaiter; future is rejected with
ThreadTransferException - fix(threadpool): fatal cause delivery — coroutine-mode tasks no longer silently resolve to null on a fatal; cause is built on the healthy parent side from a
pestrdup'd message (not from the dying worker's allocator) - fix(threadpool): snapshot UAF + libuv loop leak on fatal — op_array name strings materialized into refcounted heap strings;
uv_asynchandles (ThreadChannel/slot_event) now properly disposed on bailout throughSUSPEND
Windows fixes
- fix(win): sendfile to socket via
TransmitFile—uv_fs_sendfileto a TCP socket was broken on Windows (Winsock SOCKET ≠ CRT fd); replaced withTransmitFile
Cross-thread memory safety
- fix: closure captured-variable names UAF in
spawn_thread— interned string keys from parent's table were stored in worker snapshot; freed when parent ended; now copied into private persistent strings
Test stabilisation
- Stabilised two race-flaky tests surfaced by CI on Windows (
063-bootloader_exception,034-stdio_fopen_fwrite)
Requires
- php-src ABI v0.20.0 (
zend_async_scope_await_after_cancellation_fn) — ship withphp-8.6.0-trueasync-0.7.1
v0.7.0 — ThreadPool, request scope, stability hardening
First stable release since v0.6.7 — a large capability + hardening release that folds in the entire 0.7.0 alpha/beta/rc cycle. Headlines: a real OS-thread ThreadPool (per-worker bootloaders, coroutine-mode tasks, thread channels), request-level scope (Async\request_context()), opt-in PDO prepared-statement pooling (~2.9×), three-layer channel/pool deadlock protection, and a deep stability pass driven by a new randomized chaos test suite (now 100% public-API coverage). ABI bumped to v0.19.0.
This runtime powers the high-performance HTTP/1.1 · HTTP/2 · HTTP/3 application server true-async/server, released in lockstep as v0.7.2.
⚠️ Breaking changes
new Scope()now defaults to Not-Safe disposal. A fresh rootScopeno longer setsDISPOSE_SAFELY:dispose()cancels its coroutines synchronously instead of leaving zombies. Main scope andScope::inherit(...)chains are unchanged. Migration:(new Scope())->allowZombies()to keep the old behavior.- For API/reactor consumers: ABI 0.15 → 0.19 (unified thread-pool factory;
zend_async_io_registergainssendfile_fn/fs_open_fn;io_closedfield on IO/UDP reqs);TaskGroup/TaskSetseal()→close(),isSealed()→isClosed(); cross-thread transfer now rejects closures that declare classes/functions (file:line).
Added — multithreading
Async\ThreadPool— pool of OS threads for PHP closures:submit()/map()/close()(graceful)/cancel()(rejects backlog), counters,Countable;ThreadPoolExceptionwhen closed.- Workers auto-detect (
workers: 0→available_parallelism()), per-workerbootloaderhook, andcoroutine: truemode (each task is a coroutine in its own child scope — mayawait/use channels/IO).cancel()in coroutine mode actually kills in-flight tasks. Async\ThreadChannel— thread-safe channel via deep-copy snapshot; send/recv suspend the coroutine, not the OS thread (ThreadChannelException).- C-only
ThreadPool::submit_internal; cross-thread top-level zval transfer helpers.
Added — concurrency & pooling
- Request-level scope —
Async\request_context(): ?Context, O(1)ZEND_ASYNC_REQUEST_SCOPE(#105). This is what the true-async/serverHttpServerConfig::setRequestScope()knob builds on. - Channel deadlock protection (3 layers): per-channel
noProducerTimeout/noConsumerTimeout, global soft-timer resolver, owner-scope auto-close; typedAsync\ChannelCloseReason. - PDO Pool prepared-statement cache —
PDO::ATTR_POOL_STMT_CACHE_SIZE(pgsql/mysql/sqlite), per-conn LRU with transparent plan-invalidation retry; ~2.9× on a tight prepare+execute+fetch loop. - PDO_SQLite connection pool (
PDO::ATTR_POOL_ENABLED). TaskGroup/TaskSetqueueLimitbackpressure (default2 × concurrency).- Timer rearm / multishot API.
Added — I/O & introspection
- Async
sendfile(uv_fs_sendfile) and asyncopen(2). Async\available_parallelism()(respects cgroup quota/affinity); CPU probes —CpuSnapshot::now(),cpu_usage(),loadavg()(ZTS-safe;nullon Windows where N/A).
Changed
TaskGroup/TaskSetseal()→close()terminology.fuzzy_tests/→fuzzy-tests/.
Performance
- +32% RPS on a minimal HTTP handler — static TSRMLS cache (
ZEND_ENABLE_STATIC_TSRMLS_CACHE) turns everyEG()/ASYNC_G()into a single__threadload.
Fixed
Deep stability pass (full per-bug detail in CHANGELOG.md):
- Thread pool / cross-thread transfer — worker crash on
exit()/die()in task/bootloader (#154), bootloader-error swallowing (#154), cross-thread task UAF under cancel-vs-blocked-worker (#146),$this-bound closure SEGV, enum singleton identity, self-referential object cycles, type-info deep-copy (~12k→175k req/s under load). - Reactor / I/O — close-mid-read hangs parked reader (#144), concurrent writes to one file/descriptor losing data or corrupting heap on macOS/Windows (#129), writer not waking on peer reset, double-stop event underflow, Windows TCP accept.
- Channels / pools —
Channel(0)close-vs-send split-brain (#127) and missing rendezvous (#108), pool deadlock on broken-release (#141),recvAsync()/pool GC-cycle leaks. - curl —
curl_multi_selectcancel UAF (#145), async-read dangling subscription, callback exception leaks (#118). - JIT — tracing-JIT stale-FP spill SEGV in chaos fuzz (#118).
- Leaks — OpenSSL 3 per-thread RCU state, scope/timer/signal/composite-exception refcounts, OOM-bailout double-warning.
- Late
await()double-throw on a coroutine that finished with an exception (#139);Async\iteraterefcount UAF (#143); BSD/Darwin signal enum values;Async\signal()clobbering worker threads (#109).
Testing & CI
- New randomized chaos suite (
fuzzy-tests/): EvilPeer + Toxiproxy transport fault injection (#127/#129), mutation-block scenarios, 100% public-API coverage (167/167). Runs underTRUE_ASYNC_SCHED=random:{1,7,42,1337}per-PR on top of the deterministic FIFO run.
Ecosystem
- true-async/server — async HTTP/1.1 · HTTP/2 · HTTP/3 server built on this runtime (v0.7.2).
- Coordinated TrueAsync 0.7.0 release also tags
php-src,phpredis,xdebug, andfrankenphp; Docker images are published from true-async/releases.
v0.6.7
TrueAsync PHP 0.6.7 (php 8.6)
Highlights
- FrankenPHP on Windows — both Release and Debug builds of FrankenPHP are now produced and shipped alongside the regular PHP artifacts.
- Installer: optional FrankenPHP install — the Windows PowerShell installer now asks (or reads
INSTALL_FRANKENPHP=true) whether to also install FrankenPHP next tophp.exe.
Async extension
Added
- PDO Pool:
getAttribute()support for pool attributes.$pdo->getAttribute(PDO::ATTR_POOL_ENABLED)now returnstrue/falsedepending on whether the connection pool is active.PDO::ATTR_POOL_MINandPDO::ATTR_POOL_MAXreturn the configured pool size limits (orfalsewhen pooling is disabled).PDO::ATTR_POOL_HEALTHCHECK_INTERVALis a construction-only attribute and raises an error if read at runtime.
Fixed
- Heap-use-after-free in
await_all()/await_*()with string keys. When anyawait_*function received an array with non-interned string keys (e.g. fromjson_decode()orstr_repeat()), the returned results/errors arrays had incorrect refcount on those keys. Root cause:async_waiting_callback_disposewas called twice per callback (once fromzend_async_callbacks_removeduringdel_callback, once fromZEND_ASYNC_EVENT_CALLBACK_RELEASE), but did not checkref_count— it unconditionally calledzval_ptr_dtoron the key each time, decrementing the string refcount twice instead of once. When the calling function's local variables were freed (i_free_compiled_variables), the already-freed string was accessed again. Fixed by adding aref_countguard toasync_waiting_callback_dispose: whenref_count > 1, decrement and return without touching resources; cleanup happens only on the final dispose (ref_count == 1).
Windows build
Added
- FrankenPHP support for Windows (Release and Debug). Both build types now produce a slim
*-frankenphp.zipaddon archive containing only FrankenPHP-specific files:frankenphp.exe,libwatcher-c.dll,brotli*.dllandpthreadVC3.dll. The PHP runtime DLL and extensions are not duplicated — extract this archive on top of the main PHP package. - Debug FrankenPHP linkage. FrankenPHP's cgo configuration was split into
cgo_windows.go/cgo_windows_debug.gogated by thezend_debugGo build tag. When building against a debug devel pack, the workflow passes-tags zend_debugso clang sees-DZEND_DEBUG=1, matching the signatures of_emalloc/_efree/_estrdupinphp8ts_debug.lib(which gainZEND_FILE_LINE_DCarguments in debug mode). - Installer: optional FrankenPHP install.
installer/install.ps1now exposes anINSTALL_FRANKENPHPenvironment variable and an interactive "Install FrankenPHP?" prompt. When enabled, the installer downloads the matching slim frankenphp addon archive (respecting the Release/Debug choice), verifies its checksum against the samesha256sums.txt, and extracts it on top of the main install —frankenphp.exelands next tophp.exe.
Fixed
- Clang/MSVC ABI mismatch in
frankenphp_extension.c. Calls toemalloc(sizeof(zval))were routed throughzend_alloc.h's__builtin_constant_pspecialization to_emalloc_16, which MSVC-builtphp8ts.libdoes not export. Replaced withsafe_emalloc(1, sizeof(zval), 0)to hit the exported_safe_emallocentry point. strtok_runresolved on Windows. The Windows CRT has nostrtok_r. Replaced all four call sites infrankenphp_extension.cwithphp_strtok_r(portable wrapper frommain/php_reentrancy.h).- Debug packaging hardcoded release DLL names. The Package FrankenPHP step tried to copy
php8ts.dll, but debug builds shipphp8ts_debug.dll(seewin32/build/confutils.jsPHPLIB). Packaging now globsphp*ts*.dllandphp*ts*.libso both release and debug names work.
Installation
Windows (PowerShell)
irm https://raw.githubusercontent.com/true-async/releases/master/installer/install.ps1 | iexTo also install FrankenPHP non-interactively:
$env:INSTALL_FRANKENPHP="true"; irm https://raw.githubusercontent.com/true-async/releases/master/installer/install.ps1 | iexDocker
docker pull trueasync/php-true-async:0.6.7-php8.6
docker pull trueasync/php-true-async:latestv0.6.6
What's Changed
- libuv bumped to 1.52.1 — versions below 1.52.1 have known IO-URING issues on Linux; all UNIX/Linux builds and Docker images now ship with the fixed version.
- OPcache production config in Docker images — both
php-true-async(Debian) and FrankenPHP images now ship with a tunedopcache.iniout of the box: JIT tracing mode,validate_timestamps=0(files don't change in containers), 256 MB bytecode cache, 128 MB JIT buffer.
Performance Benchmarks
We ran a realistic benchmark against Laravel + PostgreSQL (10 SQL queries per request, 1000 req/s constant load, 30s duration) comparing TrueAsync against Swoole NTS, Swoole ZTS, and FrankenPHP Octane.
Throughput (16 workers)
| Server | req/s | vs TrueAsync | Dropped requests |
|---|---|---|---|
| TrueAsync | 993 req/s | — | 1.1% |
| Swoole NTS | 599 req/s | TrueAsync +66% | ~38% |
| Swoole ZTS | 601 req/s | TrueAsync +65% | ~36% |
| FrankenPHP Octane | 556 req/s | TrueAsync +79% | ~41% |
TrueAsync handles the full target load with just 4 workers. Blocking servers need ~25 workers to reach the same throughput — a 6× worker efficiency advantage.
Latency at 4 workers
| Server | P50 | P95 |
|---|---|---|
| TrueAsync | 28 ms | 60 ms |
| Swoole NTS | 5,440 ms | 5,630 ms |
| Swoole ZTS | 5,320 ms | 5,520 ms |
| FrankenPHP Octane | 5,240 ms | 5,390 ms |
The entire difference is queue wait: with 4 blocking workers at 1000 req/s, requests sit in queue for ~5,400 ms. TrueAsync coroutines handle requests immediately — queue wait is ~0 ms.
Memory footprint (idle)
| Server | 4 workers | 16 workers |
|---|---|---|
| TrueAsync | 147 MB | 326 MB |
| Swoole NTS | 481 MB (+227%, 3.3×) | 762 MB (+134%, 2.3×) |
| Swoole ZTS | 512 MB (+248%, 3.5×) | 765 MB (+135%, 2.3×) |
| FrankenPHP Octane | 357 MB (+143%, 2.4×) | 421 MB (+29%, 1.3×) |
Each Swoole worker bootstraps its own full copy of the Laravel application (~22 MB/worker). TrueAsync coroutines share the bootstrap within a single worker — ~2.5 MB per coroutine, ~89% less than Swoole (9× lighter).
Full results and charts: https://github.com/YanGusik/ta_benchmark/blob/main/RESULTS.md
Installation
Docker (fastest)
# Standard PHP CLI / FPM image
docker run --rm trueasync/php-true-async:latest php -r "var_dump(extension_loaded('true_async'));"
# FrankenPHP async server
docker run --rm -p 8080:8080 trueasync/php-true-async:latest-frankenphpLinux (Ubuntu / Debian)
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-linux.sh | bashmacOS
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-macos.sh | bashWindows
Download the pre-built ZIP from the releases page.
Verify installation
var_dump(extension_loaded('true_async')); // bool(true)
var_dump(ZEND_THREAD_SAFE); // bool(true)Full Changelog: v0.6.5...v0.6.6
v0.6.5
What's Changed
FrankenPHP Async Worker
v0.6.5 ships with full support for FrankenPHP in async worker mode — a single PHP thread now handles many concurrent requests, each running as a coroutine. While one coroutine is waiting for I/O (database query, HTTP call, file read), the scheduler runs other coroutines on the same thread.
Traditional FPM / standard FrankenPHP:
1 request → 1 thread (blocked during I/O)
TrueAsync FrankenPHP:
N requests → 1 thread (coroutines, non-blocking I/O)
Performance
- Waker inline storage optimization: Embedded 2 trigger slots and 2 callback slots directly into the
Wakerstruct, eliminating heap allocations for the most common case (1–2 events per await). Benchmarks:await2.13 → 0.67 μs (~3×),await_all×2 3.88 → 1.38 μs (~3×),Channelsend/recv 1.48 → 0.50 μs (~3×). - Adaptive fiber pool sizing: The fiber context pool now grows dynamically based on coroutine queue pressure instead of a fixed pool size of 4. Yields 10–15% improvement in context switch throughput (10k coroutines × 10 suspends: 490 → 566 switches/ms).
Special thanks to YanGus for helping the project!
Async Laravel is in progress
https://github.com/YanGusik/laravel-spawn/
Right now, Laravel is being adapted for TrueAsync,
which, according to preliminary tests, can deliver a 3x or greater improvement in I/O performance!
And they said this technology wasn’t needed by anyone :)
Changed
ZEND_ASYNC_SUSPENDno longer throws an error when called with an empty array of events.
Fixed
- SIGSEGV in pool healthcheck callback: Corrupted pool event structure fields caused a segfault when the pool was closed. Fixed by embedding a proper
zend_async_event_callback_tinsideasync_pool_t. proc_close()crash when child process already reaped: HandledECHILDinasync_wait_process()andlibuv_process_event_start().- Pool acquire with failed factory caused use-after-free: Fixed by checking
EG(exception)after factory failure and returning immediately. - Missing exception checks in pool error paths: Added
EG(exception)checks in healthcheck loop and other error paths. - Pool
close()now chains destructor exceptions viaprevious: All resources are destroyed and exceptions are chained viazend_exception_set_previous()instead of being silently discarded. - Pool destructor exceptions now propagate: Removed silent suppression via
zend_clear_exception().
Installation
Docker (fastest)
# Standard PHP CLI / FPM image
docker run --rm trueasync/php-true-async:latest php -r "var_dump(extension_loaded('true_async'));"
# FrankenPHP async server
docker run --rm -p 8080:8080 trueasync/php-true-async:latest-frankenphpLinux (Ubuntu / Debian)
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-linux.sh | bashTo include FrankenPHP:
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-linux.sh | \
BUILD_FRANKENPHP=true NO_INTERACTIVE=true bashmacOS
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-macos.sh | bashWindows
Download the pre-built ZIP from the releases page.
Verify installation
var_dump(extension_loaded('true_async')); // bool(true)
var_dump(ZEND_THREAD_SAFE); // bool(true)Full Changelog: v0.6.4...v0.6.5
v0.6.3
What's Changed
Fixed
-
Scope::awaitCompletion()deadlock:async_scope_notify_coroutine_finished()was not callingscope_check_completion_and_notify(), soawaitCompletion()always waited the full timeout even after all coroutines had finished. Also fixedawaitAfterCancellationto useZEND_ASYNC_WAKER_DESTROYand correctly checkzend_async_resume_whenreturn value. -
Scope dispose use-after-free:
scope_disposenow keepsref_count=1as a guard during disposal and drops it only beforeefree. Removes a prematureDEL_REFthat caused use-after-free whenfinallyhandlers created child scopes.finally_handlers_iterator_dtornow usesZEND_ASYNC_SCOPE_RELEASEto avoid double-decrement. -
Poll event leak on negative stream timeout: In
network_async.c, a negativetv_seccaused a poll event refcount leak. Fixed by guarding against negative timeout values.
Full Changelog: v0.6.2...v0.6.3
v0.6.2
What's Changed
Added
- Non-blocking
flock():flock()no longer blocks the event loop. The lock operation is offloaded to the libuv thread pool viazend_async_task_t, allowing other coroutines to continue executing while waiting for a file lock. zend_async_task_new()API: New factory function for creating thread pool tasks, registered through the reactor like timer and IO events. Replaces manualpecalloc+ field initialization.
Fixed
await_*()deadlock with already-completed awaitables: When a coroutine or Future passed toawait_all(),await_any_or_fail(), or otherawait_*()functions had already completed, it was skipped entirely (ZEND_ASYNC_EVENT_IS_CLOSED→continue), butresolved_countwas never incremented. Sincetotalstill counted the skipped awaitable,resolved_countcould never reachtotal, causing a deadlock. Fixed by usingZEND_ASYNC_EVENT_REPLAYto synchronously replay the stored result/exception through the normal callback path, correctly updating all counters. Additionally, when replay satisfies the waiting condition early (e.g.await_any_or_failneeds only one result), the loop now breaks immediately instead of subscribing to remaining awaitables and suspending unnecessarily.
Full Changelog: v0.6.1...v0.6.2
TrueAsync v0.6.1
Install
Windows
| Build | Download |
|---|---|
| Release | php-trueasync-0.6.1-php8.6-windows-x64.zip |
| Debug | php-trueasync-0.6.1-php8.6-windows-x64-debug.zip |
| Checksums | sha256sums.txt |
Quick install (PowerShell):
irm https://raw.githubusercontent.com/true-async/releases/master/installer/install.ps1 | iexDocker (Linux)
docker pull trueasync/php-true-async:0.6.1-php8.6
docker run --rm trueasync/php-true-async:0.6.1-php8.6 php -v| Tag | Description |
|---|---|
0.6.1-php8.6 |
Ubuntu 24.04 |
0.6.1-php8.6-alpine |
Alpine |
0.6.1-php8.6-debug |
Debug build |
Build from Source (Linux)
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-linux.sh | bashBuild from Source (macOS)
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-macos.sh | bashDocumentation
Full documentation, API reference, and guides at true-async.github.io
What's Changed
Fixed
feof()on sockets unreliable on Windows:WSAPoll(timeout=0)fails to detect FIN packets on Windows, causingfeof()to return false on closed sockets. Fixed by skipping poll for liveness checks (value==0) and going directly torecv(MSG_PEEK). On Windows,MSG_DONTWAITis unavailable, so non-blocking mode is temporarily toggled viaioctlsocket. Errno is saved immediately afterrecvbecauseioctlsocketclearsWSAGetLastError(). Shared logic extracted intophp_socket_check_liveness()innetwork_async.cto eliminate duplication betweenxp_socket.candxp_ssl.c.- Pipe close error on Windows:
php_select()incorrectly skipped signaled pipe handles whennum_read_pipes >= n_handles, causing pipe-close events to be missed andproc_openreads to hang. Fixed by removing thenum_read_pipes < n_handlesguard soPeekNamedPipeis always called for signaled handles.
Full Changelog: v0.6.0...v0.6.1
TrueAsync v0.6.0
Install
Windows
| Build | Download |
|---|---|
| Release | php-trueasync-0.6.0-php8.6-windows-x64.zip |
| Debug | php-trueasync-0.6.0-php8.6-windows-x64-debug.zip |
| Checksums | sha256sums.txt |
Quick install (PowerShell):
irm https://raw.githubusercontent.com/true-async/releases/master/installer/install.ps1 | iexDocker (Linux)
docker pull trueasync/php-true-async:0.6.0-php8.6
docker run --rm trueasync/php-true-async:0.6.0-php8.6 php -v| Tag | Description |
|---|---|
0.6.0-php8.6 |
Ubuntu 24.04 |
0.6.0-php8.6-alpine |
Alpine |
0.6.0-php8.6-debug |
Debug build |
Build from Source (Linux)
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-linux.sh | bashBuild from Source (macOS)
curl -fsSL https://raw.githubusercontent.com/true-async/releases/master/installer/build-macos.sh | bashDocumentation
Full documentation, API reference, and guides at true-async.github.io
What's New
Added
Async\OperationCanceledException: New exception class extendingAsyncCancellation, thrown when an awaited operation is interrupted by a cancellation token. The original exception from the token is always available via$previous. Affects all cancellable APIs:await(),await_*()family,Future::await(),Channel::send()/recv(),Scope::awaitCompletion()/awaitAfterCancellation(), andsignal().- TaskGroup (
Async\TaskGroup): Task pool with queue, concurrency control, and structured completion viaall(),race(),any(),awaitCompletion(),cancel(),seal(),finally(), andforeachiteration. - TaskSet (
Async\TaskSet): Mutable task collection with automatic cleanup semantics. ProvidesjoinNext(),joinAny(),joinAll()methods, plusforeachiteration with per-entry cleanup. - Deadlock diagnostics (
async.debug_deadlockINI option): When enabled (default: on), prints detailed diagnostic info on deadlock detection. - TCP/UDP Socket I/O: Efficient non-blocking TCP/UDP socket functions without poll overhead via libuv handles. Includes
sendto/recvfromfor UDP, socket options API (broadcast,multicast, TCPnodelay/keepalive). - Async File and Pipe I/O: Non-blocking I/O for plain files and pipes. Supported:
fread,fwrite,fseek,ftell,rewind,fgets,fgetc,fgetcsv,fputcsv,ftruncate,fflush,file_get_contents,file_put_contents,file(),copy,tmpfile,readfile,stream_get_contents,stream_copy_to_stream. - Pipe/Stream Read Timeout:
stream_set_timeout()now works for pipe streams (proc_openpipes, TTY). - Future Support: Full Future/FutureState implementation with
map(),catch(),finally()chains. - Channel: CSP-style message passing between coroutines with buffered/unbuffered modes, timeout support, and iterator interface.
- Pool (
Async\Pool): Resource pool with CircuitBreaker pattern. Configurable min/max size,acquire()/tryAcquire()/release(), blocking acquire with timeout, factory/destructor/healthcheck callbacks. - PDO Connection Pooling: Transparent connection pooling for PDO with per-coroutine dispatch.
- PDO PgSQL: Non-blocking query execution for PostgreSQL PDO driver.
- PostgreSQL: Concurrent
pg_*query execution with separate connections per async context. Async\iterate(): Iterates over an iterable with optional concurrency limit andcancelPendingparameter.Async\FileSystemWatcher: Persistent filesystem watcher withforeachiteration andAwaitableinterface.Async\signal(): One-shot signal handler returning aFuture. Supports optionalCancellation.- Acting coroutine for error context:
zend_async_globals_t.acting_coroutine— error reporting falls back to the coroutine suspendedexecute_data. Zero-cost.
Changed
- Breaking:
onFinally()renamed tofinally()onAsync\CoroutineandAsync\Scope.- Migration: Replace
->onFinally(fn)with->finally(fn).
- Migration: Replace
- Breaking:
Async\CancellationErrorrenamed toAsync\AsyncCancellation, now extends\Cancellationinstead of\Error.\Cancellationis a new PHP core root class per the True Async RFC.- Migration: Replace
catch(Async\CancellationError $e)withcatch(Async\AsyncCancellation $e)orcatch(\Cancellation $e).
- Migration: Replace
- Bailout handling: During bailout (OOM), PHP-level handlers are skipped. Removed spurious graceful shutdown warning.
- TaskGroup completion semantics:
ASYNC_TASK_GROUP_F_COMPLETEDset only when sealed and all tasks settled.
Fixed
- Async file IO position tracking: Replaced bare
lseek/_lseeki64withzend_lseek. Fixed append-mode offset init and fseek behavior on Windows. - Reactor deadlock on pending file I/O:
uv_fs_*requests were invisible toZEND_ASYNC_ACTIVE_EVENT_COUNT, causing premature reactor exit. - Generator segfault in fiber-coroutine mode: NULL
execute_datadereference inzend_generator_resumeduring shutdown. Fixed by checkingZEND_ASYNC_CURRENT_COROUTINEwithZEND_COROUTINE_IS_FIBER. - exec() output not split into lines in async path: On-the-fly line parser with zero-copy optimization, matching POPEN path behavior.
- exec() exit code race condition:
exec_on_exitis now the sole notification point. - Deadlock in
proc_close()on Windows: Job Objects sent duplicate exit events, causing premature reactor shutdown. - Use-after-free in
zend_exception_set_previous: Added identity checks before all calls. - Memory leak of
Async\DeadlockErrorin scheduler fiber exit path. stream_select()ignoring PHP-buffered data: Fixed hangs inrun-tests.php -jon macOS.- Waker events not cleaned on resume outside scheduler.
- False deadlock detection: Added
ZEND_ASYNC_REACTOR_LOOP_ALIVE()check. - TaskSet auto-cleanup race condition: Deferred cleanup to point of result delivery.
- Windows concurrent append (known limitation): Marked XFAIL (test 069).