Hi internals,
I hope you're all doing well. Before putting a formal RFC up for
discussion, I'd like to share the idea behind it and ask for your advice
on how best to frame it.
The idea
Drawing on the implementation experience of the TrueAsync project, the
proposal changes the PHP engine so that concurrency can be implemented
correctly - by an extension. The core itself gains no scheduler, no
reactor, and no event system. Instead, it learns to behave properly when
one is present: the main execution flow becomes a coroutine, the garbage
collector and the shutdown/destructor machinery become coroutine-aware,
and a coroutine context preserves engine state across switches. All
scheduling policy lives in a separate Scheduler extension that plugs into
a small set of engine slots.
Quite a lot of the engine work needed to make this real is already in
place: free switching between execution contexts (any coroutine can
switch to any other, without the asymmetric restrictions of the Fiber
API), a garbage collector that stays correct in the presence of
coroutines, and a carefully worked-out object destruction cycle at every
stage of the request lifecycle. In short - everything that gives PHP
code stability in a concurrent environment. The details are described
in this article:
https://medium.com/p/d4a38e7f6a08
The long-term goal is full asynchrony in the core: within concurrent
code, the standard blocking functions (sleep, file and socket I/O, DNS,
streams) become non-blocking transparently, without any changes to
existing userland code.
Worth mentioning: the reactor side (the I/O event loop) can be seen as a
natural continuation of the already accepted Polling API RFC
(implemented in PHP 8.6):
https://wiki.php.net/rfc/poll_api
What the PR currently contains
The implementation branch ships the engine changes plus two reference
implementations:
-
A minimal C scheduler (ext/test_scheduler) that drives the test suite.
-
The Scheduler Hook API (ext/async_scheduler_hook) - a bridge that
makes it possible to implement a Scheduler directly in PHP: the engine
hands coroutine lifecycle events to userland callbacks, and the
mandate closures (bindEntry / switchTo / currentCoroutine) give the
PHP scheduler control over context switching. Working examples live in
the PR itself, e.g. the mini scheduler used by its tests:
https://github.com/true-async/php-src/tree/async-core/ext/async_scheduler_hook/tests
Materials, if you'd like to take a closer look:
Article on the design: https://medium.com/p/d4a38e7f6a08
RFC draft:
https://github.com/true-async/php-async-core-rfc/blob/main/scheduler_rfc.md
Pull request: https://github.com/php/php-src/pull/22561
Implementation branch:
https://github.com/true-async/php-src/tree/async-core
Where I would appreciate advice
My current inclination is to propose only the engine-side changes and not
to include the Scheduler Hook API in the core. My reasoning is simple: if
something isn't required by the core and PHP works without it, there's no
reason to ship it in the core.
However, that leaves me with an RFC that contains no user-visible changes
to PHP at all - no new classes, no new functions, no new syntax. I'm
honestly not sure how such an RFC is best presented, so I'd be grateful
for your thoughts on a few questions:
-
Is an RFC the right vehicle for changes that are purely internal to
the engine (essentially an ABI for extensions)? Is there a precedent
for framing one this way? -
If so, what should the RFC text focus on - the engine behavior
changes, the ABI contract, or both? -
Should the Scheduler Hook bridge be mentioned in the RFC as an
illustration of how the ABI can be used, or left out entirely?
Of course, any feedback on the design itself is very welcome as well.
Thank you for your time - I really appreciate it.
Best regards,
Edmond