Re: RFC proposal: worker mode primitives for SAPIs
| From: | Daniil Gentili | Date: | Thu, 01 Jan 1970 00:00:00 +0000 |
| Subject: | Re: RFC proposal: worker mode primitives for SAPIs | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-122113@lists.php.net to get a copy of this message | ||
Hi,
I've been lurking around in the discussion for a while, and I'd like to chime in now since
the main, glaring issue with this proposal has only been explicitly mentioned by Rowan Tomminis,
with that subthread quickly degenerating with misguided replies.
PHP as a language absolutely needs a persistent worker mode like in all other backend languages,
which will greatly improve performance by avoiding all overhead related to request initialization.
However, the current proposal of adding support for worker mode SAPIs based on the usual PHP
superglobals is fundamentally incompatible with concurrency (aka async, aka swoole, amphp,
fiber-based concurrent runtimes in general), and I believe would steer the PHP ecosystem in the
wrong direction.
Rowan already offered a nice example of why earlier in the thread, but as an explanation to those
that may not have understood that, so I whipped up a briefer at https://gist.github.com/danog/f0e9103e6a7e1bcec1b92c411a3c4607
So no, unlike what I've read in another message here, "The nodejs curse [...] where async
and co may actually slow down your whole node." is simply false for the majority of modern
usecases, because the majority of modern applications is I/O-intensive, not CPU-intensive.
Implementing a worker API the way it is proposed is fundamentally incompatible with userland
cooperative schedulers aka event loops like amphp or reactphp, and each request would be handled,
just like in php-fpm, in a separate OS thread, not in a much more efficient userland thread.
Cooperative schedulers implemented outside of PHP in the form of an extension such as swoole would
also not be able to easily make use of the initially proposed API to handle requests in separate
coroutines, even if the handle_request function was intercepted by swoole to return control to the
swoole event loop (which would already make it superfluous as a PHP API), since the proposal
involves messing around with the usual superglobals, which as mentioned by Rowan would require
manual copying of the superglobals before using them in a newly spawned swoole coroutine.
I believe that a much, much better way of implementing a worker mode in PHP core would be to provide
a fiber-aware function/static method returning, upon receiving a request, a class implementing the
PSR-7 RequestInterface.
Fiber-aware here means that the function should behave in a way similar to how all native PHP I/O
functions should behave in an upcoming Native Event Loop RFC I'm planning to make.
The idea here is to make all I/O related functions, including this new wait_request function,
completely async in a manner that is compatible with fibers and pure PHP event loops like amphp.
It would essentially work as follows: an event loop like uv is integrated within PHP itself at least
for the native stream wrapper (I.e. all filesystem and network functions, file_get_contents and so
on).
Extensions will be provided an API to interact with the event loop.
I'm currently finalizing the details, and might probably make a first RFC in the coming weeks,
suffice to say that everything will work in a manner very similar to how async works in both the
ext-pq and php-tokio extensions, albeit with fiber integration: a separate event loop (let's
call it native) is started in another OS thread (like with ext-pq or php-tokio, WITHOUT the need for
ZTS, as no PHP data structures are modified in the native event loop), events are sent via a single
descriptor from the native event loop to the userland event loop (I.e. amphp).
If running inside a fiber, tasks are queued to the native event loop and the fiber is suspended with
a NativeSuspension, then resumed by the userland event loop.
If not running inside a fiber, tasks are queued to the native event loop and control is returned to
the userland event loop.
Essentially, my proposal is almost exactly what is already being done in https://github.com/danog/php-tokio, but
library-agonstic and implemented inside of PHP itself, perhaps even using Rust and tokio as well.
I feel that the current direction of the discussion of PHP worker modes is wrong, as it is mostly
ignoring the presence of a perfectly usable concurrency API in PHP (fibers): this has potential to
cause a big split in the PHP ecosystem, unless PHP finally fully embraces concurrency and async with
a native event loop proposal, like the one I intend to present soon.
I hope that further discussion of a worker mode continues with an eye to the future with native
concurrency :)
Regards,
Daniil Gentili.
P. S. Just in case to avoid confusion for some, frankenphp does NOT add native concurrency to php
using goroutines, as control to the go event loop is only returned when calling functions like
header or echo which cause a suspension of the goroutine because an I/O operation is made on the go
side, but
1) That's incompatible with fibers, and not just because of the minor go compatibility bug
which is being fixed, but because the go event loop is not integrated with any php event loop, and
thus does not know to return control to any eventual php fiber when needed, and will just block all
PHP fibers until that single go I/O operation is done (unlike in php-tokio, where the rust tokio
event loop is fully integrated with the php event loop (currently just revolt)).
2) It doesn't even replace the native stream wrapper with go-backed async I/O like swoole does
(though both this and php event loop integration like in php-tokio is fully possible in frankenphp
with a bit of work, I could actually lend a hand if needed...)