Repository navigation
Conversation
Introduce `async function` / `async fn` (functions, methods, closures and
arrow functions) and an `await` expression, backed by a small Fiber-based
scheduler in Zend/zend_async.c.
* Calling an async function runs its body in a new Fiber and returns an
Async\Future. The task starts eagerly and runs until its first await on
a pending Future.
* `await $future` inside a task suspends the task; outside of a task it
drives the scheduler until the Future completes. The Future's value is
returned, or its exception rethrown.
* New API: final class Async\Future (isDone()), Async\async(),
Async\wait() (the function form of `await`), Async\delay() and
Async\suspend().
* `async` is a contextual keyword: it is only a keyword when followed by
"function" or "fn", so async() remains usable as a function name.
`await` is a new reserved keyword that remains usable as a member name.
* Both constructs are desugared in the parser:
async function f($a): T { body }
becomes
function f($a): \Async\Future {
return \Async\async(function () use (&$a): T { body });
}
and `await expr` becomes `\Async\wait(expr)`, so no new opcodes are needed.
This is a prototype for discussion and not an RFC implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
|
Related feature-request issue for discussion: #24624 |
3 of 4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds a working prototype of
async/awaitto the Zend Engine, built on the existing Fiber implementation. It's opened as a draft to start discussion; I know language changes need an RFC and a vote, and this isn't meant to be merged as-is.Syntax
async function, async methods (public async function),async function () use (...) {},async fn() => ..., andstaticvariants.await <expr>: a unary expression.asyncis contextual: it's only a keyword beforefunction/fn, so userlandasync()functions keep working.awaitis a reserved keyword. It's still allowed as a method, constant or property name, and namespaced names likeAmp\Future\await()still lex as one token.Semantics
Fiberand returns anAsync\Future. The task starts eagerly and runs until it awaits a Future that hasn't completed yet.awaitinside a task suspends that task. Outside a task (top-level script or a plainFiber),awaitdrives the scheduler until the Future completes: ready tasks resume FIFO, and when nothing is ready it sleeps until the next timer.await. A Future can be awaited more than once.Errorinstead of hanging.finallyblocks run.Runtime API (
Zend/zend_async.stub.php)final class Async\Future { isDone(): bool }: not constructible, cloneable or serializable.Async\async(callable $callback, mixed ...$args): FutureAsync\wait(Future $future): mixed: the function form ofawait. It can't be namedawaitbecause that's now a keyword.Async\delay(float $seconds): FutureAsync\suspend(): void: yields to other ready tasks.Implementation
Everything is desugared in the parser, so there are no new opcodes and no VM changes:
Zend/zend_language_scanner.l/zend_language_parser.y:T_ASYNC,T_AWAITand the grammar rules.Zend/zend_async.c: AST helpers plus the scheduler (ready queue, timers, fiber→task map, GC handler).__construct/__destruct/__clone, by-reference returns, and generators.T_ASYNC,T_AWAIT).Known limitations / open questions
func_get_args()inside an async function body returns[], because the body runs in an inner closure.{closure}frame.Async\Future. The declaredTapplies to the inner task.awaitreserved is a BC break for unqualified functions namedawait(and tools such as nikic/PHP-Parser need to learn the new token).delay) and cooperativesuspend(). A real proposal would need an event-loop or reactor story. See the separate True Async RFC.Test plan
Zend/tests/async/cover basics, concurrency, exceptions, methods, closures, arrow functions, by-ref params, contextualasync,suspend(), deadlock detection, compile errors,delay()validation, shutdown cleanup,exit()inside a task, GC cycles, tokens, and awaiting from a plain Fiber.opcache.enable_cli=1 opcache.jit=tracing.run-tests.phpon a--disable-all --enable-tokenizerdebug build on macOS arm64: no regressions against the unmodified baseline. The only existing test that changed isext/reflection/tests/bug74454.phpt, whose parse-error message now lists"async".🤖 Generated with Claude Code