Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets you
keep a PHP application in memory to handle multiple HTTP requests.
Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.
In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.
According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.
These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.
The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.
In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.
SAPIs will still have to handle fetching requests from the web server and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be used),
but almost everything else could be shared.
For reference, here's the FrankenPHP code I propose to integrate into
libphp: https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245
The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-apps
I'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?
If there's interest, I can work on a patch.
Cheers,
Kévin Dunglas
Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets you
keep a PHP application in memory to handle multiple HTTP requests.Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.SAPIs will still have to handle fetching requests from the web server and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be used),
but almost everything else could be shared.For reference, here's the FrankenPHP code I propose to integrate into
libphp: https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-appsI'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?If there's interest, I can work on a patch.
Cheers,
In concept, I love this and would be ecstatic to see it happen.
In practice, I want to understand the implications for user-space code. Does this mean FPM could be configured in a way to execute a file like that shown in the docs page above? Or would it only work with third party SAPIs like FrankenPHP? I assume the handler function would be differently named. Is passing in super-globals the right/best way to handle each request, or would it be sensible to have some other abstraction there? (Whether a formal request object a la PSR-7 or something else.) To what extent would user-space code run this way have to think about concurrency, shared memory, persistent SQL connections, etc? Does it have any implications for fiber-using async code?
Depending on the details, this could be like fibers but for 3rd party SAPIs (something about 4 people in the world actually care about directly, everyone else just uses Revolt, Amp, or React, but mostly it doesn't get used), or completely changing the way 90% of the market runs PHP, which means frameworks will likely adapt to use that model primarily or exclusively (ie, less of a need for a "compile" step as a generated container or dispatcher is just held in memory automatically already). The latter sounds exciting to me, but I'm not sure which is your intent, so I don't know if I'm going too far with it. :-)
Please advise on what the implications would be for the non-SAPI-developing PHP devs out there.
--Larry Garfield
On Sun, Dec 24, 2023 at 4:21 PM Larry Garfield larry@garfieldtech.com
wrote:
In practice, I want to understand the implications for user-space code.
Does this mean FPM could be configured in a way to execute a file like that
shown in the docs page above? Or would it only work with third party SAPIs
like FrankenPHP?
In theory, PHP-FPM and the Apache module could - like all other SAPIs - be
enhanced to add a worker mode operating as described in the FrankenPHP doc
thanks to these new primitives.
However, I suggest doing this as a second step, because as described in my
first post, it will still be the responsibility of each SAPI to manage
long-running processes and communication with them. This is simple to do
with Go's GoRoutine and Rust's asynchronous runtimes such as Tokio, it's
definitely more difficult in cross-platform C. I suggest starting by adding
the primitives to libphp, then we'll see how to exploit them (and whether
it's worthwhile) in the built-in SAPIs.
I personally have less interest in working on FPM/CGI/mod_php as the other
possibilities offered by modern SAPIs like FrankenPHP are more important
(better deployment experience as you have a single static binary or Docker
image, Early Hints support, high-quality native HTTP/3 server etc), but I'd
be happy to help if anyone wants to update these SAPIs.
I assume the handler function would be differently named.
I suggest naming the function handle_request() or something similar and
using the same name for all SAPIs, so the same worker script will work
everywhere. I'll update FrankenPHP to use the "standard" name.
Is passing in super-globals the right/best way to handle each request, or
would it be sensible to have some other abstraction there? (Whether a
formal request object a la PSR-7 or something else.)
Passing super-globals is at the same time the most interoperable solution
(it allows using almost all existing PHP libraries in worker mode without
any change to them), and also allows to reuse of the existing C code.
Transforming super-globals in HttpFoundation, PSR-7, or other objects is
straightforward and can entirely be done userland (it's already what the
Symfony Runtime Component and Laravel Octane do), so there is no need to
"bloat" the C code.
Having more high-level data structures to manipulate HTTP messages similar
to HttpFoundation or PSR-7 in the language could be nice (and is in my
opinion needed), but is a separate topic.
If PHP adds a new abstraction for that at some point, it will be easy to
add support for them both in standard and worker mode.
To what extent would user-space code run this way have to think about
concurrency, shared memory, persistent SQL connections, etc? Does it have
any implications for fiber-using async code?
Regarding concurrency, it doesn't change much (it's similar to existing
SAPI). Regarding memory and SQL connections, extra care is required. Memory
leaks (and other kinds of leaks) should be avoided (or workers should
restart from time to time, which is obviously a poorer solution). Libraries
maintaining SQL connections such as Doctrine or Eloquent must ensure that
the connection isn't active.
The good news is that thanks to RoadRunner, Swoole, Laravel Octane, Symfony
Runtime etc... Most popular libraries are already compatible with
long-running processes, and most issues have been fixed.
Some old apps and libraries will probably never be updatable, but that's
not a big issue because this feature will be entirely opt-in.
Fibers work as expected. There is a small limitation when using them with
Go (that is being tracked in the Go runtime,
https://frankenphp.dev/docs/known-issues/#fibers), but it's not related to
the C code of the worker mode, and this limitation shouldn't exist for
SAPIs not written in Go.
Depending on the details, this could be like fibers but for 3rd party
SAPIs (something about 4 people in the world actually care about directly,
everyone else just uses Revolt, Amp, or React, but mostly it doesn't get
used), or completely changing the way 90% of the market runs PHP, which
means frameworks will likely adapt to use that model primarily or
exclusively (ie, less of a need for a "compile" step as a generated
container or dispatcher is just held in memory automatically already). The
latter sounds exciting to me, but I'm not sure which is your intent, so I
don't know if I'm going too far with it. :-)
My intent is that most SAPIs expose the same (or a very similar
interoperable) worker mode. So (I hope) that most PHP developers will not
have to deal with these primitives directly, but that it will allow a new
generation of super-fast PHP apps to be created. Most frameworks already
support that but require a lot of boilerplate code to support the different
existing engines. Standardizing will likely increase adoption and will
allow collaboration to make the low-level code that I propose to move in
libphp as fast, stable, and clean as possible.
Please advise on what the implications would be for the
non-SAPI-developing PHP devs out there.
None, except that they will be able to use the new handle_request()
function to create a worker script if supported by the SAPI they use.
However, I suggest doing this as a second step, because as described in
my first post, it will still be the responsibility of each SAPI to
manage long-running processes and communication with them. This is
simple to do with Go's GoRoutine and Rust's asynchronous runtimes such
as Tokio, it's definitely more difficult in cross-platform C. I suggest
starting by adding the primitives to libphp, then we'll see how to
exploit them (and whether it's worthwhile) in the built-in SAPIs.
I personally have less interest in working on FPM/CGI/mod_php as the
other possibilities offered by modern SAPIs like FrankenPHP are more
important (better deployment experience as you have a single static
binary or Docker image, Early Hints support, high-quality native HTTP/3
server etc), but I'd be happy to help if anyone wants to update these
SAPIs.
To what extent would user-space code run this way have to think about concurrency, shared memory, persistent SQL connections, etc? Does it have any implications for fiber-using async code?
Regarding concurrency, it doesn't change much (it's similar to existing
SAPI). Regarding memory and SQL connections, extra care is required.
Memory leaks (and other kinds of leaks) should be avoided (or workers
should restart from time to time, which is obviously a poorer
solution). Libraries maintaining SQL connections such as Doctrine or
Eloquent must ensure that the connection isn't active.
Forgive my ignorance, but why no connection? You mean the pre-worker-start part needs to avoid an SQL connection? Why is that? That would be something that needs to be super-well documented, and possibly some guards in place to prevent it, if there's no good way around it. (This is the sort of detail I'm thinking of, where I just don't know the implications but want to think through them as much as possible in advance, so that it can be "safe by design.")
Fibers work as expected. There is a small limitation when using them
with Go (that is being tracked in the Go runtime,
https://frankenphp.dev/docs/known-issues/#fibers), but it's not related
to the C code of the worker mode, and this limitation shouldn't exist
for SAPIs not written in Go.Depending on the details, this could be like fibers but for 3rd party SAPIs (something about 4 people in the world actually care about directly, everyone else just uses Revolt, Amp, or React, but mostly it doesn't get used), or completely changing the way 90% of the market runs PHP, which means frameworks will likely adapt to use that model primarily or exclusively (ie, less of a need for a "compile" step as a generated container or dispatcher is just held in memory automatically already). The latter sounds exciting to me, but I'm not sure which is your intent, so I don't know if I'm going too far with it. :-)
My intent is that most SAPIs expose the same (or a very similar
interoperable) worker mode. So (I hope) that most PHP developers will
not have to deal with these primitives directly, but that it will allow
a new generation of super-fast PHP apps to be created. Most frameworks
already support that but require a lot of boilerplate code to support
the different existing engines. Standardizing will likely increase
adoption and will allow collaboration to make the low-level code that I
propose to move in libphp as fast, stable, and clean as possible.
Do you have an intent or expectation of a worker-style SAPI being shipped with PHP itself, or for that to remain the domain of third parties?
Please advise on what the implications would be for the non-SAPI-developing PHP devs out there.
None, except that they will be able to use the new handle_request()
function to create a worker script if supported by the SAPI they use.
I mean more what implications would there be on how user-space code is written to be worker-SAPI-friendly. (The SQL connection comment above, for example.) I have not worked with any of the worker-ish tools so far myself, so other than "you'll need an alternate index.php for that", I don't have a good sense of what else I'd want to do differently to play nice with Franken and Friends.
The idea of combining fiber-based code with supported worker-mode runners sounds like a ridiculously cool future for PHP, but I don't know how windy that path is. :-)
--Larry Garfield
Forgive my ignorance, but why no connection? You mean the
pre-worker-start part needs to avoid an SQL connection? Why is that? That
would be something that needs to be super-well documented, and possibly
some guards in place to prevent it, if there's no good way around it.
(This is the sort of detail I'm thinking of, where I just don't know the
implications but want to think through them as much as possible in advance,
so that it can be "safe by design.")
Sorry, I made a typo. I mean "libraries must ensure that the connection
is active" (if the connection timeout has been reached, the library
must reconnect).
Your worker script will be long-running code, as in Java, Go, etc. So if it
depends on external services, it must check that the connection is still
active, and reconnect if necessary.
This is the default in most languages, but not in PHP (yet).
Do you have an intent or expectation of a worker-style SAPI being shipped
with PHP itself, or for that to remain the domain of third parties?
As I tried to explain in my previous message, this could be nice, and
possible, but I don't plan to do it myself for now :)
I mean more what implications would there be on how user-space code is
written to be worker-SAPI-friendly. (The SQL connection comment above, for
example.) I have not worked with any of the worker-ish tools so far
myself, so other than "you'll need an alternate index.php for that", I
don't have a good sense of what else I'd want to do differently to play
nice with Franken and Friends.
As far as I know, there are no other implications than memory (and other
resources) leaks (https://laravel.com/docs/10.x/octane#managing-memory-leaks)
and timeout handling.
The idea of combining fiber-based code with supported worker-mode runners
sounds like a ridiculously cool future for PHP, but I don't know how windy
that path is. :-)
That already works if you use FrankenPHP! Joe also experimented
successfully using the parallel extension instead of Fibers:
https://twitter.com/krakjoe/status/1587234661696245760
On Sun, Dec 24, 2023 at 4:21 PM Larry Garfield larry@garfieldtech.com
wrote:In practice, I want to understand the implications for user-space code.
Does this mean FPM could be configured in a way to execute a file like
that
shown in the docs page above? Or would it only work with third party
SAPIs
like FrankenPHP?In theory, PHP-FPM and the Apache module could - like all other SAPIs - be
enhanced to add a worker mode operating as described in the FrankenPHP doc
thanks to these new primitives.
I have been thinking about something similar for FPM and if you had some
sort pool manager process, you could maybe do some sort of initial
execution but then it gets really tricky especially with sharing resources
and managing connections. I think it would be a big can of worms so I don't
think this is going to happen anytime soon. I could imaging that there will
be similar issues for Apache prefork which is likely the most used MPM for
legacy apps. Effectively it means that this function won't be working on
most installations as two of the likely most used SAPI's won't support it.
I think it should be pretty clear from the beginning.
However, I suggest doing this as a second step, because as described in my
first post, it will still be the responsibility of each SAPI to manage
long-running processes and communication with them. This is simple to do
with Go's GoRoutine and Rust's asynchronous runtimes such as Tokio, it's
definitely more difficult in cross-platform C. I suggest starting by adding
the primitives to libphp, then we'll see how to exploit them (and whether
it's worthwhile) in the built-in SAPIs.
The problem with this is that we would add some code that won't be used by
any of the built in SAPI which means that that we won't be able to have
automated tests for this. So the minimum should be to have at least one
core SAPI supporting this new functionality. I wouldn't mind if it's just a
SAPI for testing purpose which might be actually useful for testing embed
SAPI code. I think that should be a requirement for accepting a PR
introducing this.
It would be also good to put together some base design PR for this as
currently SAPI common functions are implemented separately in each SAPI
(e.g. apache_request_headers). From the linked functionality, it is is not
a big amount of code and seems somehow specific to the FrankenPHP so why
couldn't each SAPI just implement this function separately? I know that
this is not ideal but it's what is already used for apache_request_headers.
I think otherwise you would need some hooking mechanism that should have
some default (which would probably just throw exception) because it is not
going to be implemented by all SAPI's. I think it would be really good if
you could provide more details about planned implementation for this.
I personally have less interest in working on FPM/CGI/mod_php as the other
possibilities offered by modern SAPIs like FrankenPHP are more important
(better deployment experience as you have a single static binary or Docker
image, Early Hints support, high-quality native HTTP/3 server etc)
Except that those are all threaded SAPIs so they offer less separation and
protection against application crashes in addition to the fact that thread
management in PHP still has got its own issues. They are certainly some
advantages especially for thin services but if you have huge monolith
codebase like some big CMS and other projects, then I would probably stick
with process separation model.
Cheers
Jakub
On Sun, Dec 24, 2023 at 4:21 PM Larry Garfield larry@garfieldtech.com
wrote:In practice, I want to understand the implications for user-space code.
Does this mean FPM could be configured in a way to execute a file like
that
shown in the docs page above? Or would it only work with third party
SAPIs
like FrankenPHP?In theory, PHP-FPM and the Apache module could - like all other SAPIs - be
enhanced to add a worker mode operating as described in the FrankenPHP doc
thanks to these new primitives.I have been thinking about something similar for FPM and if you had some
sort pool manager process, you could maybe do some sort of initial
execution but then it gets really tricky especially with sharing resources
and managing connections. I think it would be a big can of worms so I don't
think this is going to happen anytime soon. I could imaging that there will
be similar issues for Apache prefork which is likely the most used MPM for
legacy apps. Effectively it means that this function won't be working on
most installations as two of the likely most used SAPI's won't support it.
I think it should be pretty clear from the beginning.However, I suggest doing this as a second step, because as described in my
first post, it will still be the responsibility of each SAPI to manage
long-running processes and communication with them. This is simple to do
with Go's GoRoutine and Rust's asynchronous runtimes such as Tokio, it's
definitely more difficult in cross-platform C. I suggest starting by
adding
the primitives to libphp, then we'll see how to exploit them (and whether
it's worthwhile) in the built-in SAPIs.The problem with this is that we would add some code that won't be used by
any of the built in SAPI which means that that we won't be able to have
automated tests for this. So the minimum should be to have at least one
core SAPI supporting this new functionality. I wouldn't mind if it's just a
SAPI for testing purpose which might be actually useful for testing embed
SAPI code. I think that should be a requirement for accepting a PR
introducing this.It would be also good to put together some base design PR for this as
currently SAPI common functions are implemented separately in each SAPI
(e.g. apache_request_headers). From the linked functionality, it is is not
a big amount of code and seems somehow specific to the FrankenPHP so why
couldn't each SAPI just implement this function separately? I know that
this is not ideal but it's what is already used for apache_request_headers.
I think otherwise you would need some hooking mechanism that should have
some default (which would probably just throw exception) because it is not
going to be implemented by all SAPI's. I think it would be really good if
you could provide more details about planned implementation for this.I personally have less interest in working on FPM/CGI/mod_php as the other
possibilities offered by modern SAPIs like FrankenPHP are more important
(better deployment experience as you have a single static binary or Docker
image, Early Hints support, high-quality native HTTP/3 server etc)Except that those are all threaded SAPIs so they offer less separation and
protection against application crashes in addition to the fact that thread
management in PHP still has got its own issues. They are certainly some
advantages especially for thin services but if you have huge monolith
codebase like some big CMS and other projects, then I would probably stick
with process separation model.Cheers
Jakub
Sure, the main targets are new SAPIs like FrankenPHP and the one in Rust
developed by the RoadRunner team. I thought it was clear in my previous
messages but I'll be glad to make it bold in the RFC.
Automated tests (likely through a test SAPI) will definitely be needed.
Throwing if the current SAPI doesn't support (yet) the new userland
function looks sensitive.
Couldn't this shared code be put in "main", as it could (theoretically, I
agree that it will be hard to do for existing core SAPIs) be used by all
SAPIs?
On Sun, Dec 24, 2023 at 4:21 PM Larry Garfield larry@garfieldtech.com
wrote:In practice, I want to understand the implications for user-space code.
Does this mean FPM could be configured in a way to execute a file like
that
shown in the docs page above? Or would it only work with third party
SAPIs
like FrankenPHP?In theory, PHP-FPM and the Apache module could - like all other SAPIs -
be
enhanced to add a worker mode operating as described in the FrankenPHP
doc
thanks to these new primitives.I have been thinking about something similar for FPM and if you had some
sort pool manager process, you could maybe do some sort of initial
execution but then it gets really tricky especially with sharing resources
and managing connections. I think it would be a big can of worms so I don't
think this is going to happen anytime soon. I could imaging that there will
be similar issues for Apache prefork which is likely the most used MPM for
legacy apps. Effectively it means that this function won't be working on
most installations as two of the likely most used SAPI's won't support it.
I think it should be pretty clear from the beginning.However, I suggest doing this as a second step, because as described in
my
first post, it will still be the responsibility of each SAPI to manage
long-running processes and communication with them. This is simple to do
with Go's GoRoutine and Rust's asynchronous runtimes such as Tokio, it's
definitely more difficult in cross-platform C. I suggest starting by
adding
the primitives to libphp, then we'll see how to exploit them (and whether
it's worthwhile) in the built-in SAPIs.The problem with this is that we would add some code that won't be used
by any of the built in SAPI which means that that we won't be able to have
automated tests for this. So the minimum should be to have at least one
core SAPI supporting this new functionality. I wouldn't mind if it's just a
SAPI for testing purpose which might be actually useful for testing embed
SAPI code. I think that should be a requirement for accepting a PR
introducing this.It would be also good to put together some base design PR for this as
currently SAPI common functions are implemented separately in each SAPI
(e.g. apache_request_headers). From the linked functionality, it is is not
a big amount of code and seems somehow specific to the FrankenPHP so why
couldn't each SAPI just implement this function separately? I know that
this is not ideal but it's what is already used for apache_request_headers.
I think otherwise you would need some hooking mechanism that should have
some default (which would probably just throw exception) because it is not
going to be implemented by all SAPI's. I think it would be really good if
you could provide more details about planned implementation for this.I personally have less interest in working on FPM/CGI/mod_php as the
other
possibilities offered by modern SAPIs like FrankenPHP are more important
(better deployment experience as you have a single static binary or
Docker
image, Early Hints support, high-quality native HTTP/3 server etc)Except that those are all threaded SAPIs so they offer less separation
and protection against application crashes in addition to the fact that
thread management in PHP still has got its own issues. They are certainly
some advantages especially for thin services but if you have huge monolith
codebase like some big CMS and other projects, then I would probably stick
with process separation model.Cheers
Jakub
Sure, the main targets are new SAPIs like FrankenPHP and the one in Rust
developed by the RoadRunner team. I thought it was clear in my previous
messages but I'll be glad to make it bold in the RFC.
The way how I read was that this would be eventually supported by all SAPIs
which I think is not likely going to be the case.
Automated tests (likely through a test SAPI) will definitely be needed.
Throwing if the current SAPI doesn't support (yet) the new userland
function looks sensitive.
+1
Couldn't this shared code be put in "main", as it could (theoretically, I
agree that it will be hard to do for existing core SAPIs) be used by all
SAPIs?
The main does not have a module but it could be probably added to standard
ext like it's the case for other functionality related to main.
Cheers
Jakub
Hi Jakub,
I have been thinking about something similar for FPM and if you had some
sort pool manager process, you could maybe do some sort of initial
execution but then it gets really tricky especially with sharing resources
and managing connections. I think it would be a big can of worms so I don't
think this is going to happen anytime soon.
It's already happening. Most libraries (particularly popular ones in
the Symfony/Laravel world) already support most of this
out-of-the-box. I love stateless servers, but sometimes a stateful
server is better. What is really nice about worker mode is that you
can actually have a PHP-native in-memory database that only exists for
as long as the worker is running (I do this for some unit tests).
I could imaging that there will
be similar issues for Apache prefork which is likely the most used MPM for
legacy apps. Effectively it means that this function won't be working on
most installations as two of the likely most used SAPI's won't support it.
I think it should be pretty clear from the beginning.
Most people are familiar with fastcgi_finish_request() and there are
some built-in SAPI's that don't support it. I don't think that just
because it won't start out with full support, it should be discarded.
It would be also good to put together some base design PR for this as
currently SAPI common functions are implemented separately in each SAPI
(e.g. apache_request_headers). From the linked functionality, it is is not
a big amount of code and seems somehow specific to the FrankenPHP so why
couldn't each SAPI just implement this function separately? I know that
this is not ideal but it's what is already used for apache_request_headers.
I think otherwise you would need some hooking mechanism that should have
some default (which would probably just throw exception) because it is not
going to be implemented by all SAPI's. I think it would be really good if
you could provide more details about planned implementation for this.
Most (all?) modern SAPI (lightspeed, roadrunner, etc) implements
fastcgi_finish_request(), even if no fastcgi is involved, simply
because of backward compatibility. It'd be great to actually bikeshed
a decent name and syntax/semantics before something popular comes
along and forces us all to use frankenphp_handle_request() or
something, simply because of backward compatibility.
I think this functionality unlocks some really cool potential powers
(in-memory databases for development, connection pooling without an
extension, etc) and it's worth at least seriously considering
implementing it in core.
- Rob
Hi,
On Tue, Dec 26, 2023 at 12:09 AM Robert Landers landers.robert@gmail.com
wrote:
Hi Jakub,
I have been thinking about something similar for FPM and if you had some
sort pool manager process, you could maybe do some sort of initial
execution but then it gets really tricky especially with sharing
resources
and managing connections. I think it would be a big can of worms so I
don't
think this is going to happen anytime soon.It's already happening. Most libraries (particularly popular ones in
the Symfony/Laravel world) already support most of this
out-of-the-box. I love stateless servers, but sometimes a stateful
server is better. What is really nice about worker mode is that you
can actually have a PHP-native in-memory database that only exists for
as long as the worker is running (I do this for some unit tests).
This was just about FPM. What I meant that this is not going to be
supported by FPM anytime soon if ever.
I could imaging that there will
be similar issues for Apache prefork which is likely the most used MPM
for
legacy apps. Effectively it means that this function won't be working on
most installations as two of the likely most used SAPI's won't support
it.
I think it should be pretty clear from the beginning.Most people are familiar with fastcgi_finish_request() and there are
some built-in SAPI's that don't support it. I don't think that just
because it won't start out with full support, it should be discarded.
I didn't mean that it shouldn't be added. It should just be clear from the
beginning that this will be for limited set of SAPIs.
It would be also good to put together some base design PR for this as
currently SAPI common functions are implemented separately in each SAPI
(e.g. apache_request_headers). From the linked functionality, it is is
not
a big amount of code and seems somehow specific to the FrankenPHP so why
couldn't each SAPI just implement this function separately? I know that
this is not ideal but it's what is already used for
apache_request_headers.
I think otherwise you would need some hooking mechanism that should have
some default (which would probably just throw exception) because it is
not
going to be implemented by all SAPI's. I think it would be really good if
you could provide more details about planned implementation for this.Most (all?) modern SAPI (lightspeed, roadrunner, etc) implements
fastcgi_finish_request(), even if no fastcgi is involved, simply
because of backward compatibility. It'd be great to actually bikeshed
a decent name and syntax/semantics before something popular comes
along and forces us all to use frankenphp_handle_request() or
something, simply because of backward compatibility.
I agree that coming up with some sensible name and API would be a good
think even though it will have limited use in core. And if it gives us some
coverage of embed SAPI (libphp), then it's even better.
Cheers
Jakub
Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets you
keep a PHP application in memory to handle multiple HTTP requests.Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.SAPIs will still have to handle fetching requests from the web server and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be used),
but almost everything else could be shared.For reference, here's the FrankenPHP code I propose to integrate into
libphp: https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-appsI'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?If there's interest, I can work on a patch.
Cheers,
Kévin Dunglas
Much like Larry, I'm curious what sort of scope you imagine for this. Are
you imagining something that is geared specifically towards HTTP requests,
or would this be a more generic "PHP Application Worker" that might be
spawned to handle other types of applications? Could we have a worker
listen to a specific port and respond to or handle all requests on that
port/device?
Jordan
On Sun, Dec 24, 2023 at 10:44 PM Jordan LeDoux jordan.ledoux@gmail.com
wrote:
Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets you
keep a PHP application in memory to handle multiple HTTP requests.Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.SAPIs will still have to handle fetching requests from the web server and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be used),
but almost everything else could be shared.For reference, here's the FrankenPHP code I propose to integrate into
libphp: https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-appsI'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?If there's interest, I can work on a patch.
Cheers,
Kévin Dunglas
Much like Larry, I'm curious what sort of scope you imagine for this. Are
you imagining something that is geared specifically towards HTTP requests,
or would this be a more generic "PHP Application Worker" that might be
spawned to handle other types of applications? Could we have a worker
listen to a specific port and respond to or handle all requests on that
port/device?Jordan
Ho Jordan,
Yes, the scope I imagine is geared specifically towards HTTP requests.
Something more generic than common primitives for SAPIs and a shared public
API to handle HTTP requests with a long-running PHP worker script will be
hard to do outside of SAPIs because they depend on a lot of external
concerns such as the programming language the SAPI is using.
On Sun, Dec 24, 2023 at 10:44 PM Jordan LeDoux jordan.ledoux@gmail.com
wrote:Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets you
keep a PHP application in memory to handle multiple HTTP requests.Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.SAPIs will still have to handle fetching requests from the web server and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be
used),
but almost everything else could be shared.For reference, here's the FrankenPHP code I propose to integrate into
libphp:
https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-appsI'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?If there's interest, I can work on a patch.
Cheers,
Kévin Dunglas
Much like Larry, I'm curious what sort of scope you imagine for this. Are
you imagining something that is geared specifically towards HTTP requests,
or would this be a more generic "PHP Application Worker" that might be
spawned to handle other types of applications? Could we have a worker
listen to a specific port and respond to or handle all requests on that
port/device?Jordan
Ho Jordan,
Yes, the scope I imagine is geared specifically towards HTTP requests.
Something more generic than common primitives for SAPIs and a shared public
API to handle HTTP requests with a long-running PHP worker script will be
hard to do outside of SAPIs because they depend on a lot of external
concerns such as the programming language the SAPI is using.
So you want to introduce a SAPI that doesn't work with any of the existing
HTTP solutions people use that only supports HTTP requests? Or am I
misunderstanding something?
This sounds a bit like you want to merge in a tool that is designed for
your personal product directly into core. FrankenPHP may be incredible and
awesome, but the world runs on Apache and Nginx for HTTP requests.
Jordan
On Mon, Dec 25, 2023 at 7:56 PM Jordan LeDoux jordan.ledoux@gmail.com
wrote:
On Sun, Dec 24, 2023 at 10:44 PM Jordan LeDoux jordan.ledoux@gmail.com
wrote:Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets
you
keep a PHP application in memory to handle multiple HTTP requests.Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.In addition to FrankenPHP, projects such as RoadRunner and Swoole
provide
engines supporting worker modes.According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.In addition to sharing code, maintenance, performance optimization,
etc.,
the existence of a common infrastructure would standardize the way
worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.SAPIs will still have to handle fetching requests from the web server
and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be
used),
but almost everything else could be shared.For reference, here's the FrankenPHP code I propose to integrate into
libphp:
https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-appsI'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?If there's interest, I can work on a patch.
Cheers,
Kévin Dunglas
Much like Larry, I'm curious what sort of scope you imagine for this.
Are you imagining something that is geared specifically towards HTTP
requests, or would this be a more generic "PHP Application Worker" that
might be spawned to handle other types of applications? Could we have a
worker listen to a specific port and respond to or handle all requests on
that port/device?Jordan
Ho Jordan,
Yes, the scope I imagine is geared specifically towards HTTP requests.
Something more generic than common primitives for SAPIs and a shared public
API to handle HTTP requests with a long-running PHP worker script will be
hard to do outside of SAPIs because they depend on a lot of external
concerns such as the programming language the SAPI is using.So you want to introduce a SAPI that doesn't work with any of the existing
HTTP solutions people use that only supports HTTP requests? Or am I
misunderstanding something?This sounds a bit like you want to merge in a tool that is designed for
your personal product directly into core. FrankenPHP may be incredible and
awesome, but the world runs on Apache and Nginx for HTTP requests.Jordan
As explained in the initial message and in my reply to Jakub, the main
targets are emerging SAPIs. We have no interest (quite the contrary) in
moving this code from FrankenPHP to PHP core (harder maintenance, slower
iterations as more collaboration will be involved...), but I do think that
having a "standard" and shared infrastructure and API for worker modes
between new generation SAPIs will be beneficial to the community as a whole
(no need - as at present - to write a different worker script for each
engine having a worker mode, sharing of optimizations, security patches
etc...).
We're talking roughly about a C function of a few dozen lines, not
something very big.
Hello,
So you want to introduce a SAPI that doesn't work with any of the existing
HTTP solutions people use that only supports HTTP requests? Or am I
misunderstanding something?This sounds a bit like you want to merge in a tool that is designed for
your personal product directly into core. FrankenPHP may be incredible and
awesome, but the world runs on Apache and Nginx for HTTP requests.
The world was running on cgi when php was created and a few years after.
Then the world was running on threads/apache. Then it ran in fcgi and
finally fcgi fpm for the last few years.
Other languages support this since quite some time. Projects like reactphp,
swoole and the likes provide userland versions of it and the benefits are
clear. They even worked together recently to get a common core. Many
(large) projects out there already support FrankenPHP, the needs are there.
FrankenPhp and a worker mode go beyond the classical php usages we had.
Desktop applications or IoT are getting more common (see laravel's
nativephp f.e.). I have done something similar for one of our products
using embedded sapi but how it is done using frankenphp would be an order
of magnitude better.
Having the complex parts in the core us a good start to experiment, adapt.
Later a core sapi may be added, we cannot know without trying and let the
community uses it.
best,
Pierre
So you want to introduce a SAPI that doesn't work with any of the existing
HTTP solutions people use that only supports HTTP requests? Or am I
misunderstanding something?This sounds a bit like you want to merge in a tool that is designed for
your personal product directly into core. FrankenPHP may be incredible and
awesome, but the world runs on Apache and Nginx for HTTP requests.
To be honest, after trying RoadRunner (an alternative to FrankenPHP), I'm
using it for all my projects if the framework and packages have no issues
with this execution model. Only old codebases with extensive usage of
global variables and stateful services are at risk. If you stick with
readonly classes and follow best DI practices, there will be minimum to no
issues. Symfomy is actively propagating safe coding styles. So from my POV
it is not a problem that a new execution model will be supported. If your
code is not compatible - then this is your problem. It is normal that some
code may not work properly in different modes. The fact that the world is
running on Apache and Nginx doesn't mean this will be forever with no
alternatives. The fact that there are multiple projects with the same goal
shows that there is a big demand for PHP speedup. And there are no visible
alternatives except a worker mode. IMO.
Hi
So you want to introduce a SAPI that doesn't work with any of the existing
HTTP solutions people use that only supports HTTP requests? Or am I
misunderstanding something?This sounds a bit like you want to merge in a tool that is designed for
your personal product directly into core. FrankenPHP may be incredible and
awesome, but the world runs on Apache and Nginx for HTTP requests.
My understanding of the proposal in very simplified language is: A new
function shall be added to be implemented by SAPIs that are capable of
implementing the function correctly.
This function effectively:
- Flushes the pending headers (header() calls) and body (if any).
- Blocks until a request arrives.
- Sets the superglobals ($_GET, $_POST, ...) to the request's values.
- Sets the php://input stream to that request's body.
- Sets up default headers (e.g. x-powered-by, if enabled).
Best regard
Tim Düsterhus
Why not just build this into the embed sapi instead? since its only for
third party sapis?
Hello and Merry Christmas!
One of the main features of FrankenPHP is its worker mode, which lets you
keep a PHP application in memory to handle multiple HTTP requests.Worker modes are becoming increasingly popular in the PHP world. Symfony
(Runtime Component), Laravel (Octane), and many projects based on these
frameworks (API Platform, Sulu...) now support a worker mode.In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.According to benchmarks, worker modes can improve the performance of PHP
applications by up to 15 times.
In addition to FrankenPHP, which is basically a SAPI for Go's integrated
web server, a new generation of SAPIs is currently under development.
Several SAPIs written in Rust (including one by the RoadRunner team) are
currently under development.These SAPIs, along with existing SAPIs, could benefit from a shared
infrastructure to build worker modes.The FrankenPHP code is written and should be easy to move around in PHP
itself, to enable other SAPIs to use it.In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.SAPIs will still have to handle fetching requests from the web server and
pausing the worker to wait for new requests (in FrankenPHP, we use
GoRoutines for this, in Rust or C, other primitives will have to be used),
but almost everything else could be shared.For reference, here's the FrankenPHP code I propose to integrate into
libphp: https://github.com/dunglas/frankenphp/blob/main/frankenphp.c#L245The public API is documented here:
https://frankenphp.dev/docs/worker/#custom-appsI'd like to hear what the community thinks about this. Would you be
interested in this functionality in PHP? Should I work on an RFC?If there's interest, I can work on a patch.
Cheers,
In addition to sharing code, maintenance, performance optimization, etc.,
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.
While this seems like a noble aim, there doesn't seem to be much
consensus on what such an API should look like; from what I can see:
- FrankenPHP expects the user to manage the main event loop, repeatedly
passing the server a function to be called once; it doesn't pass
anything into or out of the userland handler, instead resetting global
state to mimic a non-worker environment
[https://frankenphp.dev/docs/worker/#custom-apps] - RoadRunner doesn't use a callback at all, instead providing methods to
await a request and provide a response; it directly uses PSR-7 and
PSR-17 objects [https://roadrunner.dev/docs/php-worker/current/en] - OpenSwoole manages the main loop itself, and uses lifecycle events to
interface to userland code; the HTTP 'Request' event is passed custom
Request and Response objects
[https://openswoole.com/docs/modules/swoole-http-server-on-request]
It also seems relevant to mention the situation in Python:
- WSGI specifies a Python-level interface between a web server and a web
application / framework. The server side is expected to provide the
event loop (unlike in FrankenPHP), and passes the application an
environment dictionary (based on CGI) and a start_response callback.
[https://peps.python.org/pep-3333/] - The newer ASGI generalises this interface into an asynchronous event
handling system, including support for WebSockets.
[https://asgi.readthedocs.io/en/latest/introduction.html]
Out of all of these, the FrankenPHP approach seems to be the most basic,
providing good backwards compatibility with PHP's normal "shared
nothing" approach, but not much forwards compatibility - I can't see how
it would be adapted for an async PHP environment, or with WebSockets,
for instance. I'm sceptical how many SAPIs would actually implement it,
rather than providing more powerful APIs.
Regards,
--
Rowan Tommins
[IMSoP]
On Fri, Dec 29, 2023 at 8:14 PM Rowan Tommins rowan.collins@gmail.com
wrote:
- FrankenPHP expects the user to manage the main event loop, repeatedly
passing the server a function to be called once; it doesn't pass
anything into or out of the userland handler, instead resetting global
state to mimic a non-worker environment
[https://frankenphp.dev/docs/worker/#custom-apps]
This isn't exact. FrankenPHP does manage the event loop (the Go runtime
manages it - through a channel - under the hood).
The frankenphp_handle_request() pauses the thread until the Go runtime
gives back control to the C thread (when a request is dispatched to this
worker).
It's actually very similar to WSGI.
As I explained in my previous messages, it's expected that other SAPIs
handle the event loop too (using the primitives provided by the language
they are written in).
- RoadRunner doesn't use a callback at all, instead providing methods to
await a request and provide a response; it directly uses PSR-7 and
PSR-17 objects [https://roadrunner.dev/docs/php-worker/current/en]- OpenSwoole manages the main loop itself, and uses lifecycle events to
interface to userland code; the HTTP 'Request' event is passed custom
Request and Response objects
[https://openswoole.com/docs/modules/swoole-http-server-on-request]
I already replied to Crell about that. It will totally possible to expose
more complex HTTP message objects in the future,
but PHP currently lacks such objects. The only things we have are
superglobals (which are more or less similar to CGI variables, as done in
WSGI) and streams. It's why we're using them.
If PHP adds a higher-level API at some point, we'll be able to upgrade this
part as every other part of the PHP code base. But it's an unrelated topic:
having such higher-level representations of HTTP messages would be
beneficial both in "normal" and in "worker" mode.
it would be adapted for an async PHP environment, or with WebSockets,
for instance.
I'm not sure what you mean by "async PHP environment".
WebSockets and WebTransport are a different kind of beast, they are much
lower level than HTTP and will require a different API anyway (and probably
a lot of other adaptations in core) to be supported in PHP.
In Go, for instance, the WebSocket and WebTransport APIs aren't the same as
the HTTP API.
Best regards,
On Fri, Dec 29, 2023 at 8:14 PM Rowan Tommins
rowan.collins@gmail.com wrote:- FrankenPHP expects the user to manage the main event loop ...
This isn't exact. FrankenPHP does manage the event loop (the Go
runtime manages it - through a channel - under the hood).
Perhaps "event loop" was the wrong term; what I was highlighting is that
to use FrankenPHP or RoadRunner, you have to write a while loop, which
explicitly handles one request at a time. In Swoole, there is no such
loop: you register event handlers and then call $server->run() once.
Similarly, WSGI mandates that the server "invokes the application
callable once for each request it receives from an HTTP client".
It's a distinction of pull/poll (the application must actively block
until next request) vs push/subscribe (the application is passively
invoked whenever needed).
I already replied to Crell about that. It will totally possible to
expose more complex HTTP message objects in the future,
but PHP currently lacks such objects. The only things we have are
superglobals (which are more or less similar to CGI variables, as done
in WSGI) and streams. It's why we're using them.
The use of objects vs arrays wasn't the main difference I was trying to
highlight there, but rather the overall API of how information gets into
and out of the application. FrankenPHP is the only server listed which
needs to reset global state on each request, because the others
(including Python WSGI and ASGI) use non-global variables for both input
and output.
I notice that the Laravel Octane adaptor for FrankenPHP takes that
global state and immediately converts it into non-global variables for
consumption by the application.
I'm not sure what you mean by "async PHP environment".
OpenSwoole, AMPHP, ReactPHP, etc - servers which expose concurrency
directly to the user of PHP. In those environments, global state isn't
just reused between consecutive requests, it's shared between multiple
requests running concurrently, so a global "current request" and
"current response" have no meaning.
WebSockets and WebTransport are a different kind of beast, they are
much lower level than HTTP and will require a different API anyway
(and probably a lot of other adaptations in core) to be supported in PHP.
WebSocket support in PHP is just as real as worker modes and
asynchronous concurrency.
Swoole has a WebSocket implementation included in core
[https://openswoole.com/docs/modules/swoole-websocket-server] and
Roadrunner has a plugin for it
[https://roadrunner.dev/docs/plugins-centrifuge/current] In both cases
(and in ASGI), the same basic API is used as with HTTP, but using a more
general concept of "events" in place of "requests".
Other PHP implementations include Ratchet [http://socketo.me/] and AMPHP
Websocket Server [https://github.com/amphp/websocket-server].
Note that both async and WebSockets were mentioned as possible "forward
compatibility". If we're talking about "next generation SAPIs", these
are the kinds of features that people will be - and already are -
developing; so it seems foolish not to at least consider them when
designing new baseline APIs.
Regards,
--
Rowan Tommins
[IMSoP]
Hi Rowan,
pt., 29 gru 2023 o 23:56 Rowan Tommins rowan.collins@gmail.com napisał(a):
...
The use of objects vs arrays wasn't the main difference I was trying to
highlight there, but rather the overall API of how information gets into
and out of the application. FrankenPHP is the only server listed which
needs to reset global state on each request, because the others
(including Python WSGI and ASGI) use non-global variables for both input
and output.
I wasn't aware of ASGI, in the past I read about WSGI and noticed a PHP
connector allowing the PHP app to run inside the WSGI server.
I read most of the spec
https://asgi.readthedocs.io/en/latest/specs/index.html yesterday and it
sounds like a really solid solution.
Personally, I'd love to see something similar for PHP.
It'd clearly be something different from the usual PHP app where global
$_GET|POST variables carry the HTTP request input.
Solution taken by Python in fact is about returning a callable fulfilling a
specific signature no matter if this is a simple function, closure or
Object implementing __invoke function - and this gives much flexibility.
I believe that considering the fact that ASGI provides an API for HTTP
interaction including WebSockets that could only benefit to PHP ecosystem.
In the past, I was thinking about something similar to adopting WSGI but
was not aware of ASGI.
Cheers,
Michał Marcin Brzuchalski
- FrankenPHP expects the user to manage the main event loop ...
This isn't exact. FrankenPHP does manage the event loop (the Go
runtime manages it - through a channel - under the hood).Perhaps "event loop" was the wrong term; what I was highlighting is that
to use FrankenPHP or RoadRunner, you have to write a while loop, which
explicitly handles one request at a time. In Swoole, there is no such
loop: you register event handlers and then call $server->run() once.
Similarly, WSGI mandates that the server "invokes the application
callable once for each request it receives from an HTTP client".It's a distinction of pull/poll (the application must actively block
until next request) vs push/subscribe (the application is passively
invoked whenever needed).
I think these models have different capabilities: A pull/poll model is
quite simple, while a subscription model is usually more complex.
With something simple like in FrankenPHP, creating a Queue SAPI, a
WebSocket SAPI, etc isn't far off, where someone writes some PHP to
consume a queue or websocket connections.
I already replied to Crell about that. It will totally possible to
expose more complex HTTP message objects in the future,
but PHP currently lacks such objects. The only things we have are
superglobals (which are more or less similar to CGI variables, as done
in WSGI) and streams. It's why we're using them.The use of objects vs arrays wasn't the main difference I was trying to
highlight there, but rather the overall API of how information gets into
and out of the application. FrankenPHP is the only server listed which
needs to reset global state on each request, because the others
(including Python WSGI and ASGI) use non-global variables for both input
and output.I notice that the Laravel Octane adaptor for FrankenPHP takes that
global state and immediately converts it into non-global variables for
consumption by the application.
For this to happen in PHP Core, there would need to be request objects
instead of a global state. If an RFC implementing PSR
requests/responses in Core is a pre-requisite for enabling what we're
discussing here, I'd personally be all for that (as would a very large
chunk of the PHP community, IMHO). I personally think this is a
chicken/egg type of problem though. It doesn't make sense to have
request/response objects right now, and I get the feeling that people
would only support worker mode primitives if there were request
objects... so, it might make sense to build a v1 of the worker mode
primitives and then iterate towards request objects, because then
there would be an actual need for them.
Robert Landers
Software Engineer
Utrecht NL
Hi Robert,
sob., 30 gru 2023, 10:59 użytkownik Robert Landers landers.robert@gmail.com
napisał:
- FrankenPHP expects the user to manage the main event loop ...
This isn't exact. FrankenPHP does manage the event loop (the Go
runtime manages it - through a channel - under the hood).Perhaps "event loop" was the wrong term; what I was highlighting is that
to use FrankenPHP or RoadRunner, you have to write a while loop, which
explicitly handles one request at a time. In Swoole, there is no such
loop: you register event handlers and then call $server->run() once.
Similarly, WSGI mandates that the server "invokes the application
callable once for each request it receives from an HTTP client".It's a distinction of pull/poll (the application must actively block
until next request) vs push/subscribe (the application is passively
invoked whenever needed).I think these models have different capabilities: A pull/poll model is
quite simple, while a subscription model is usually more complex.With something simple like in FrankenPHP, creating a Queue SAPI, a
WebSocket SAPI, etc isn't far off, where someone writes some PHP to
consume a queue or websocket connections.I already replied to Crell about that. It will totally possible to
expose more complex HTTP message objects in the future,
but PHP currently lacks such objects. The only things we have are
superglobals (which are more or less similar to CGI variables, as done
in WSGI) and streams. It's why we're using them.The use of objects vs arrays wasn't the main difference I was trying to
highlight there, but rather the overall API of how information gets into
and out of the application. FrankenPHP is the only server listed which
needs to reset global state on each request, because the others
(including Python WSGI and ASGI) use non-global variables for both input
and output.I notice that the Laravel Octane adaptor for FrankenPHP takes that
global state and immediately converts it into non-global variables for
consumption by the application.For this to happen in PHP Core, there would need to be request objects
instead of a global state. If an RFC implementing PSR
requests/responses in Core is a pre-requisite for enabling what we're
discussing here, I'd personally be all for that (as would a very large
chunk of the PHP community, IMHO). I personally think this is a
chicken/egg type of problem though. It doesn't make sense to have
request/response objects right now, and I get the feeling that people
would only support worker mode primitives if there were request
objects... so, it might make sense to build a v1 of the worker mode
primitives and then iterate towards request objects, because then
there would be an actual need for them.
That is certainly not true. Looking at WSGI or ASGI there is no need for
request response objects. These can be provided in userland which gives
more flexibility cause of different rules governing over bc break policy in
PHP core.
Name one true argument to convince me in this topic and I may change my
mind.
For the years I had the same impression but on low level the primitives are
more flexible and we all know that.
Cheers,
Michał Marcin Brzuchalski
For this to happen in PHP Core, there would need to be request objects
instead of a global state.
Again, the representation as objects isn't a key requirement. Python's WSGI spec simply has a dictionary (read: associative array) of the environment based on CGI. The application might well turn that into a more powerful object, but standardisation of such wasn't considered a pre-requisite, and would actually have hampered ASGI, where not all events represent an HTTP request.
The key requirement is that you have some way of passing the current request and response around as scoped variables, not global state. That's essential for any kind of concurrent run-time (async, thread-aware, etc).
An event / subscriber model fits well with that: the local scope for each request is set up by an invocation of the callback with defined parameters and return value.
Funnily enough, the example of a worker script for FrankenPHP does both things: it sends each request to the same application "handle" callback, passing in the super-global arrays as parameters to be used as non-global state. https://frankenphp.dev/docs/worker/#custom-apps So really all I'm arguing is that a few more lines of that PHP example be moved into the C implementation, so that the user only needs to provide that inner callable, not the outer while loop.
Regards,
--
Rowan Tommins
[IMSoP]
On 30 December 2023 09:59:07 GMT, Robert Landers
landers.robert@gmail.com wrote:For this to happen in PHP Core, there would need to be request objects
instead of a global state.Again, the representation as objects isn't a key requirement. Python's
WSGI spec simply has a dictionary (read: associative array) of the
environment based on CGI. The application might well turn that into a
more powerful object, but standardisation of such wasn't considered a
pre-requisite, and would actually have hampered ASGI, where not all
events represent an HTTP request.The key requirement is that you have some way of passing the current
request and response around as scoped variables, not global state.
That's essential for any kind of concurrent run-time (async,
thread-aware, etc).An event / subscriber model fits well with that: the local scope for
each request is set up by an invocation of the callback with defined
parameters and return value.Funnily enough, the example of a worker script for FrankenPHP does both
things: it sends each request to the same application "handle"
callback, passing in the super-global arrays as parameters to be used
as non-global state. https://frankenphp.dev/docs/worker/#custom-apps So
really all I'm arguing is that a few more lines of that PHP example be
moved into the C implementation, so that the user only needs to provide
that inner callable, not the outer while loop.
So you're suggesting something like:
$app->initializeStuffHowever();
set_event_handler(Closure $handler);
// Script blocks here until a sigkill is received, or something.
I think there's an important distinction that is getting missed in the above discussion, beyond the push-vs-pull question. FrankenPHP, as I understand it, pre-boots multiple worker processes, keeps them in memory, and then handles each request in its own process. Swoole, Amp/React/Revolt, and friends have only a single process running at all, and make use of async to simulate multiple simultaneous requests, a la NodeJs. That means mutable global variables in the FrankenPHP model still won't leak between parallel requests, whereas they absolutely would/do in a Swole/Revolt world.
I'm not going to call one of those better or worse (I don't have enough experience with either to say), but they are different beasts for which first class support would be different SAPIs either way. They're not mutually exclusive thanks to Fibers (which mean you don't need the entire call stack to be async), but you would want to pick one or the other as primary runner mode of an application. Let's keep that in mind when making comparisons.
The Franken-model is closer to how PHP-FPM works today, which means that is easier to port existing code to, especially existing code that has lots of globals or hidden globals. (Eg, Laravel.) That may or may not make it the better model overall, I don't know, but it's the more-similar model.
All that said, the idea of allowing a "persistent HTTP handler process" SAPI, "persistent Queue handler process" SAPI, and "persistent cron handler process" SAPI (or whatever combination of persistent processes) to all run side by side with the same code base but different entry point scripts is... Hot. If we can do something that would enable that kind of runtime model, I am very much here for that.
--Larry Garfield
Hi Larry,
sob., 30 gru 2023 o 20:49 Larry Garfield larry@garfieldtech.com
napisał(a):
On 30 December 2023 09:59:07 GMT, Robert Landers
landers.robert@gmail.com wrote:For this to happen in PHP Core, there would need to be request objects
instead of a global state.Again, the representation as objects isn't a key requirement. Python's
WSGI spec simply has a dictionary (read: associative array) of the
environment based on CGI. The application might well turn that into a
more powerful object, but standardisation of such wasn't considered a
pre-requisite, and would actually have hampered ASGI, where not all
events represent an HTTP request.The key requirement is that you have some way of passing the current
request and response around as scoped variables, not global state.
That's essential for any kind of concurrent run-time (async,
thread-aware, etc).An event / subscriber model fits well with that: the local scope for
each request is set up by an invocation of the callback with defined
parameters and return value.Funnily enough, the example of a worker script for FrankenPHP does both
things: it sends each request to the same application "handle"
callback, passing in the super-global arrays as parameters to be used
as non-global state. https://frankenphp.dev/docs/worker/#custom-apps So
really all I'm arguing is that a few more lines of that PHP example be
moved into the C implementation, so that the user only needs to provide
that inner callable, not the outer while loop.So you're suggesting something like:
$app->initializeStuffHowever();
set_event_handler(Closure $handler);
// Script blocks here until a sigkill is received, or something.I think there's an important distinction that is getting missed in the
above discussion, beyond the push-vs-pull question. FrankenPHP, as I
understand it, pre-boots multiple worker processes, keeps them in memory,
and then handles each request in its own process. Swoole,
Amp/React/Revolt, and friends have only a single process running at all,
and make use of async to simulate multiple simultaneous requests, a la
NodeJs. That means mutable global variables in the FrankenPHP model still
won't leak between parallel requests, whereas they absolutely would/do in a
Swole/Revolt world.I'm not going to call one of those better or worse (I don't have enough
experience with either to say), but they are different beasts for which
first class support would be different SAPIs either way. They're not
mutually exclusive thanks to Fibers (which mean you don't need the entire
call stack to be async), but you would want to pick one or the other as
primary runner mode of an application. Let's keep that in mind when making
comparisons.The Franken-model is closer to how PHP-FPM works today, which means that
is easier to port existing code to, especially existing code that has lots
of globals or hidden globals. (Eg, Laravel.) That may or may not make it
the better model overall, I don't know, but it's the more-similar model.All that said, the idea of allowing a "persistent HTTP handler process"
SAPI, "persistent Queue handler process" SAPI, and "persistent cron handler
process" SAPI (or whatever combination of persistent processes) to all run
side by side with the same code base but different entry point scripts
is... Hot. If we can do something that would enable that kind of runtime
model, I am very much here for that.
What you wrote sounds like some good points (as usual).
I'm not an expert (yet!) but was playing around with some callable trying
to mimic ASGI
https://github.com/brzuchal/asgi-playground/blob/main/app.php#L26-L37
What I think currently (maybe too hurry, but...) is that this kind of
approach is flexible enough to handle in easy way many SAPIs which identify
to app their capabilities,
and the app decides how and what can handle $scope['type']
in the example
code.
I know there is a Runtime library, that tries to integrate
Symfony/Laaravel to many SAPIs, but as far as I understood the discussion
went to figure out if there is some kind of standard approach that could be
shaped under the PHP umbrella.
Maybe this is just a temporary fascination about ASGI solution, could be.
If this is not in the scope of interest of anyone then forgive me, I won't
bother anymore.
Cheers,
Michał Marcin Brzuchalski
The Franken-model is closer to how PHP-FPM works today, which means that is easier to port existing code to, especially existing code that has lots of globals or hidden globals. (Eg, Laravel.) That may or may not make it the better model overall, I don't know, but it's the more-similar model.
That's why I said earlier that it provides better backwards compatibility - existing code which directly uses PHP's current global state can more easily be run in a worker which populates that global state.
However, the benefit is marginal, for two reasons. Firstly, because in practice a lot of applications avoid touching the global state outside of some request bootstrapping code anyway. The FrankenPHP example code and Laravel Octane both demonstrate this.
Secondly, because in an environment that handles a single request at a time, the reverse is also possible: if the server passes request information directly to a callback, that callback can populate the superglobals as appropriate. The only caveat I can think of is input streams, since userland code can't reset and populate php://input, or repoint STDOUT.
On the other hand, as soon as you have any form of concurrency, the two models are not interchangeable - it would make no sense for an asynchronous callback to read from or write to global state.
And that's what I meant about FrankenPHP's API having poor forward compatibility - if you standardise on an API that populates global state, you close off any possibility of using that API in a concurrent environment. If you instead standardise on callbacks which hold request and response information in their own scope, you don't close anything off.
If anything, calling this "forwards compatibility" is overly generous: the OP gave Swoole as an example of an existing worker environment, but I can't see any way that Swoole could implement an API that communicated request and response information via global state.
Regards,
--
Rowan Tommins
[IMSoP]
On Sun, Dec 31, 2023 at 2:20 AM Rowan Tommins rowan.collins@gmail.com
wrote:
On 30 December 2023 19:48:39 GMT, Larry Garfield larry@garfieldtech.com
wrote:The Franken-model is closer to how PHP-FPM works today, which means that
is easier to port existing code to, especially existing code that has lots
of globals or hidden globals. (Eg, Laravel.) That may or may not make it
the better model overall, I don't know, but it's the more-similar model.That's why I said earlier that it provides better backwards compatibility
- existing code which directly uses PHP's current global state can more
easily be run in a worker which populates that global state.However, the benefit is marginal, for two reasons. Firstly, because in
practice a lot of applications avoid touching the global state outside of
some request bootstrapping code anyway. The FrankenPHP example code and
Laravel Octane both demonstrate this.Secondly, because in an environment that handles a single request at a
time, the reverse is also possible: if the server passes request
information directly to a callback, that callback can populate the
superglobals as appropriate. The only caveat I can think of is input
streams, since userland code can't reset and populate php://input, or
repoint STDOUT.On the other hand, as soon as you have any form of concurrency, the two
models are not interchangeable - it would make no sense for an asynchronous
callback to read from or write to global state.And that's what I meant about FrankenPHP's API having poor forward
compatibility - if you standardise on an API that populates global state,
you close off any possibility of using that API in a concurrent
environment. If you instead standardise on callbacks which hold request and
response information in their own scope, you don't close anything off.If anything, calling this "forwards compatibility" is overly generous: the
OP gave Swoole as an example of an existing worker environment, but I can't
see any way that Swoole could implement an API that communicated request
and response information via global state.Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
This new function is intended for SAPIs. Swoole was given as an example of
worker mode, but it isn't a SAPI. AFAIK, it doesn't use the SAPI
infrastructure provided by PHP.
The scope of my proposal is only to provide a new feature in the SAPI
infrastructure to build worker modes to handle HTTP requests, not to deal
with non-SAPI engines.
That being said, I don't understand what would prevent Swoole from
implementing the proposed API, or even to implement a userland
implementation of the proposed API using Swoole under the hood.
It seems doable to emulate the sequential request handling and to create an
adapter from their custom objects to superglobals and streams.
For WebSockets and WebTransports, the same considerations apply. The SAPI
API will have to be extended to deal with such low-level network layers,
worker mode or not. To me, this is very interesting (and needed) but should
be discussed in another RFC.
As pointed out by Crell, FrankenPHP (and similar theoretical solutions)
starts as many workers as needed. This can be a fixed set of workers, as in
FrankenPHP, or a dynamic number of workers, similar to traditional FPM
workers.
FrankenPHP uses threads to parallelize request handling (to start several
instances of the worker script in parallel). Other techniques could be
used, for instance, in the future, we could use goroutines (which use a mix
of system threads and async IO, and goroutines are handled in a single
system thread:
https://github.com/golang/go/blob/master/src/runtime/HACKING.md#gs-ms-ps)
instead of threads, by adding a new backend to TSRM.
The global state is never reset in the same worker context, it is preserved
across requests, except for superglobals and streams, which are updated
with the data of the request being handled.
Superglobals are the PHP way to expose CGI-like data. Adding support for
other ways to do it such as proposed by WSGI, and/or new objects and the
like could be interesting, but again this isn't the scope of this proposal
which is narrow, and tries to reuse the existing infrastructure as much as
possible. The proposal is simple enough to support new ways if introduced
at some point in PHP, and the Symfony Runtime and Laravel Octane libraries
prove that it's possible to implement more advanced data structures
user-land on top of the existing superglobals infrastructure.
Regarding the infinite loop, we could indeed remove it using a few lines of
code. I hesitated to do that initially, but the loop gives more flexibility
by allowing the implementation of many features in user-land (like
restarting the worker after a fixed number of requests, when the memory
reaches a certain level, etc). Without this loop, all these features will
require new C functions. I thought it was a simple and good way to give
more power to user-land, at the price of some boilerplate code (that will
in theory always be hidden in low-level libraries or bootstrap code). That
being said, I've no strong opinion about that and would be open to removing
the need for the while loop if we find a good way to keep the current
flexibility.
Best regards,
This new function is intended for SAPIs. Swoole was given as an example of
worker mode, but it isn't a SAPI. AFAIK, it doesn't use the SAPI
infrastructure provided by PHP.
The scope of my proposal is only to provide a new feature in the SAPI
infrastructure to build worker modes to handle HTTP requests, not to deal
with non-SAPI engines.
One of the advantages you suggested of your proposal is that users would have a consistent way to write worker scripts. To achieve that, you want a design that can be adopted by as many implementations as possible, regardless of how they implement it. Providing helper infrastructure for that design is a secondary concern - as you admit, the actual code you're proposing to add is quite short.
That being said, I don't understand what would prevent Swoole from
implementing the proposed API
Then one of us is missing something very fundamental. As I understand it, Swoole's model is similar to that popularised by node.js: a single thread processes multiple incoming requests concurrently, using asynchronous I/O. For instance, a thread might run the following:
01 Request A received
02 Request A input validated
03 Request A sends async query to DB
04 Request A hands control to event loop while it awaits result
05 Request B received
06 Request B sends async HTTP call to some API
07 Request B awaits result
08 Request A resumed with DB result
09 Request A formats and returns response
10 Request A complete
11 Request B resumed
12 Request B fornats and returns response
Each request has its own call stack, started by a different call to the registered event handler, but any global state is shared between them - there is no actual threading going on, so no partitioned memory.
If requests are communicated by setting up superglobals, that will happen at step 01 and again at step 05. If you try to read from them at step 09, you would see them populated with information about request B, but you're trying to handle request A.
It would be possible to work around that by placing large warnings to users not to read superglobals after any async call - basically forcing them to create scoped copies to pass around. But the worse problem is output: if step 09 and step 12 both just use "echo", how do you track which output needs to go to which network connection? You can't just set up an output buffer, because that's global state shared by both call stacks. You have to put something into the scope of the call stack - a callback to write output, an expected return value, etc.
Asynchronous code ends up somewhat resembling functional programming: everything you want to have side effects on needs to be passed around as parameters and return values, because the only thing isolated between requests is local variable scope.
Regards,
--
Rowan Tommins
[IMSoP]
hello,
Then one of us is missing something very fundamental. As I understand it,
Swoole's model is similar to that popularised by node.js: a single thread
processes multiple incoming requests concurrently, using asynchronous I/O.
The nodejs curse yes, where async and co may actually slow down your whole
node.
DB result
09 Request A formats and returns response
10 Request A complete
11 Request B resumed
12 Request B fornats and returns response
php handles this in threadsafe mode, like modphp f.e. It is why frankenphp
requires a TS build of php. Requests are handled by a thread pool, not in
single thread event loop which may block all requests.
best,
Pierre
php handles this in threadsafe mode
Depending on your exact definition of "php", this is either irrelevant or just plain wrong.
If you mean "the HTTP SAPIs shipped with official builds of PHP", then it's true, none handle multiple concurrent requests in a single thread using async I/O. But none handle multiple consecutive requests in a single thread using a "worker mode" either, which is the whole point of this conversation.
If you mean for "php" to include third party HTTP handlers such as FrankenPHP, then it also includes Swoole, which is what I was describing. Please someone correct me if I'm wrong, but I understand ReactPHP and AMPHP also include HTTP servers using the same principle.
So, to reiterate my point once more: implementations of PHP using async concurrency are out there already in production use. If we're attempting to standardise a new API for worker modes (i.e. HTTP servers which are no longer "shared nothing"), choosing one which can be used by consecutive worker modes (FrankenPHP , RoadRunner) but not concurrent ones (Swoole, ReactPHP, AMPHP) feels like a big missed opportunity.
Regards,
--
Rowan Tommins
[IMSoP]
On 31 December 2023 16:31:31 GMT, Pierre Joye pierre.php@gmail.com
wrote:php handles this in threadsafe mode
Depending on your exact definition of "php", this is either irrelevant or
just plain wrong.If you mean "the HTTP SAPIs shipped with official builds of PHP", then
it's true, none handle multiple concurrent requests in a single thread
using async I/O. But none handle multiple consecutive requests in a single
thread using a "worker mode" either, which is the whole point of this
conversation.
Unless I misunderstand the current proposal, it is about providing a core
interface to allow one to create its own SAPI similar to FrankenPHP, which
does not handle request in a singe thread but a thread pool handled by go's
coroutine.
I can imagine other developers implement it using other mechanisms (rust or
c++ f.e.) but the main interface from a php internal pov remains.
It is a first step and based on the usages/feedback, the next steps could
be the second part of your comment. Or?
best,
Pierre
I'm running out of different ways to say the same thing, and not really
sure which part of my previous messages people haven't understood. I'm
really not saying anything very controversial or complicated, just "had
you considered that style B would offer these additional possibilities
over style A".
So I'm going to quote the parts of previous messages which I think
already answer the latest questions.
After that, I'm going to leave the thread to others for a bit, unless I
see a question that isn't just retreading the same ground.
Unless I misunderstand the current proposal, it is about providing a
core interface to allow one to create its own SAPI similar to
FrankenPHP, which does not handle request in a singe thread but a
thread pool handled by go's coroutine.
From Kévin's opening post:
In addition to FrankenPHP, projects such as RoadRunner and Swoole provide
engines supporting worker modes.
[...]
the existence of a common infrastructure would standardize the way worker
scripts are created and provide a high-level PHP API for writing worker
scripts that work with all SAPIs that rely on this new feature.
From my last message:
If we're attempting to standardise a new API for worker modes (i.e. HTTP servers which are no longer "shared nothing"), choosing one which can be used by consecutive worker modes (FrankenPHP , RoadRunner) but not concurrent ones (Swoole, ReactPHP, AMPHP) feels like a big missed opportunity.
I'm not sure concurrent servers would even be able to be in scope if
we wanted them to be?
From my message dated 2023-12-29 22:55 UTC:
Note that both async and WebSockets were mentioned as possible
"forward compatibility". If we're talking about "next generation
SAPIs", these are the kinds of features that people will be - and
already are - developing; so it seems foolish not to at least consider
them when designing new baseline APIs.
It is a first step and based on the usages/feedback, the next steps
could be the second part of your comment. Or?
From my message dated 2023-12-31 01:20 UTC:
if you standardise on an API that populates global state, you close off any possibility of using that API in a concurrent environment. If you instead standardise on callbacks which hold request and response information in their own scope, you don't close anything off.
And from 2023-12-30 10:53 UTC:
The key requirement is that you have some way of passing the current request and response around as scoped variables, not global state. That's essential for any kind of concurrent run-time (async, thread-aware, etc).
Regards,
--
Rowan Tommins
[IMSoP]
php handles this in threadsafe mode
Depending on your exact definition of "php", this is either irrelevant or just plain wrong.
If you mean "the HTTP SAPIs shipped with official builds of PHP", then it's true, none handle multiple concurrent requests in a single thread using async I/O. But none handle multiple consecutive requests in a single thread using a "worker mode" either, which is the whole point of this conversation.
If you mean for "php" to include third party HTTP handlers such as FrankenPHP, then it also includes Swoole, which is what I was describing. Please someone correct me if I'm wrong, but I understand ReactPHP and AMPHP also include HTTP servers using the same principle.
So, to reiterate my point once more: implementations of PHP using async concurrency are out there already in production use. If we're attempting to standardise a new API for worker modes (i.e. HTTP servers which are no longer "shared nothing"), choosing one which can be used by consecutive worker modes (FrankenPHP , RoadRunner) but not concurrent ones (Swoole, ReactPHP, AMPHP) feels like a big missed opportunity.
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
Hey Rowan,
I'm not sure concurrent servers would even be able to be in scope if
we wanted them to be? PHP doesn't have an "event-loop", concurrent
i/o, or any building blocks needed to serve concurrent requests. It's
been made possible through extensions and libraries that aren't
maintained by PHP, so I'm not sure how we'd support them directly
without those other basic functionalities. If we directly wanted to
support concurrent servers, I think there is probably a lot of work to
do at a very low level before we could realistically include them in
this part of the conversation.
Robert Landers
Software Engineer
Utrecht NL
Again, the representation as objects isn't a key requirement. Python's
WSGI spec simply has a dictionary (read: associative array) of the
environment based on CGI. The application might well turn that into a
more powerful object, but standardisation of such wasn't considered a
pre-requisite, and would actually have hampered ASGI, where not all
events represent an HTTP request.
Jumping in to add that NGINX has their newish Unit app server, which uses Yet Another Custom SAPI for PHP.
-Jeff
Again, the representation as objects isn't a key requirement. Python's
WSGI spec simply has a dictionary (read: associative array) of the
environment based on CGI. The application might well turn that into a
more powerful object, but standardisation of such wasn't considered a
pre-requisite, and would actually have hampered ASGI, where not all
events represent an HTTP request.Jumping in to add that NGINX has their newish Unit app server, which uses
Yet Another Custom SAPI for PHP.
It seems to me that its development significantly decreased after F5
takeover and the unit main developer leaving the company so not sure if
there is that much future in it.
Cheers
Jakub
Hi,
I like it for start a discussion, than it's necessary.
But we need to see the big picture.
The CLI-SAPI is the poor brother in PHP (contrary to other languages),
but that is another discussion than I'll try to open later.
Create a Worker-SAPI?
First any CLI worker can't access the SAPI. So they don't have any benefit.
So Amp, React, Revolt, Workerman, Adapterman, Symfony runtime,... can't
access the internal SAPI functions. Each need to recreate in user land
PHP code for functions that already exist in PHP sapis.
Kudos, for the RFC RFC1867 from Ilija. But we need to go farther.
It isn't possible use header functions :(,
https://github.com/php/php-src/issues/12304
Later we have SAPIs than use PHP embed (really easy to use :)) or in a
similar way.
Here we find 2 ways: forks or threads!!
With forks we can use Super-Globals, with threads it's impossible, and
for that they need to encapsulate it in Request/Response objects.
How we'll join both situations. Here start the discussion.
Forks:
Frankenphp, RoadRunner, Ngx-php (the fastest PHP runtime),...
Nginx Unit still use a shared nothing approach, but it's really easy to
have both.
Threads:
Swoole, OpenSwoole, Swoow,... in that situation the super globals are
NOT possible.
Here some frameworks permit use both (forks or threads) depending on the
master event loop that we choose. But they need to force all to the
threads way to have a unified interface.
We are talking about the main loop, because inside we can use any thread
system.
Thanks to all, and to Kevin to start the discussion.
PD: Actually any new PHP SAPI need to be added to the php-src to have
OPCache enabled. Nginx Unit and other still use cli-server SAPI to have
it. That need to be changed, so any SAPI can call it, without register.
Regards
Joan Miquel
Le 4 janv. 2024 à 18:21, Joanhey joanhey@kumbiaphp.com a écrit :
Hi,
I like it for start a discussion, than it's necessary.
But we need to see the big picture.The CLI-SAPI is the poor brother in PHP (contrary to other languages), but that is another discussion than I'll try to open later.
Create a Worker-SAPI?
First any CLI worker can't access the SAPI. So they don't have any benefit.
So Amp, React, Revolt, Workerman, Adapterman, Symfony runtime,... can't access the internal SAPI functions. Each need to recreate in user land PHP code for functions that already exist in PHP sapis.
Kudos, for the RFC RFC1867 from Ilija. But we need to go farther.
It isn't possible use header functions :(, https://github.com/php/php-src/issues/12304Later we have SAPIs than use PHP embed (really easy to use :)) or in a similar way.
Here we find 2 ways: forks or threads!!
With forks we can use Super-Globals, with threads it's impossible, and for that they need to encapsulate it in Request/Response objects.How we'll join both situations. Here start the discussion.
Forks:
Frankenphp, RoadRunner, Ngx-php (the fastest PHP runtime),...
Nginx Unit still use a shared nothing approach, but it's really easy to have both.Threads:
Swoole, OpenSwoole, Swoow,... in that situation the super globals are NOT possible.Here some frameworks permit use both (forks or threads) depending on the master event loop that we choose. But they need to force all to the threads way to have a unified interface.
We are talking about the main loop, because inside we can use any thread system.
Thanks to all, and to Kevin to start the discussion.
PD: Actually any new PHP SAPI need to be added to the php-src to have OPCache enabled. Nginx Unit and other still use cli-server SAPI to have it. That need to be changed, so any SAPI can call it, without register.
Regards
Joan Miquel
Thanks for the summary!
For the record, FrankenPHP and NGINX Unit use threads, not forks (and recommend ZTS PHP builds). And as far as I understand, Swoole etc use a reactor pattern and non-blocking IOs, not threads. Also Symfony Runtime isn’t an engine but a library with adapters for FrankenPHP, RoadRunner, Bref etc.
The main SAPI using libphp is the embed SAPI, but some non-core SAPIs including FrankenPHP, NGINX Unit and uWSGI use libphp with their own SAPIs. The confusing part is that you need to enable the embed SAPI through the configure options to build libphp, even if the embed SAPI itself isn’t used.
Best regards,
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
-
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)).
-
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...)
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
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)).
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...)
Hey Daniil,
I already said this, but to reiterate: I, personally, hear what you
are saying and largely agree with you; however, before we can really
have any kind of discussion on concurrent servers, we HAVE to address
the underlying issues that are missing from PHP. In PHP-src, there are
no such things as request objects. There are no such things as event
loops. There are fibers, but absolutely no std-library i/o functions
are using them, making it pointless to build on fibers except when
using user-land libraries that reimplemented everything (like amphp)
in pure PHP or an extension like swoole.
That is the unfortunate point we are at and it doesn't make sense to
address concurrent servers at this moment, or passing request objects.
We have a long way to go before those will be real things that we can
have a proper conversation about in the context of php-src.
Robert Landers
Software Engineer
Utrecht NL
I already said this, but to reiterate: I, personally, hear what you
are saying and largely agree with you; however, before we can really
have any kind of discussion on concurrent servers, we HAVE to address
the underlying issues that are missing from PHP. In PHP-src
So, let's address them...
there are no such things as request objects
This is a non-issue. As has been discussed already, it's perfectly fine to have an event-based system where the event details are an associative array, rather than a rich object.
There are no such things as event loops. There are fibers,
but absolutely no std-library i/o functions are using them
This is what the bulk of Daniil's email is suggesting a way to improve.
We have a long way to go before those will be real things that we can
have a proper conversation about in the context of php-src.
If we keep waiting to have the conversation, it will never happen.
And if we start building brand new APIs like infrastructure for worker-mode SAPIs, in ways that are fundamentally incompatible with async, we're just making more work for ourselves when we do get there.
Regards,
--
Rowan Tommins
[IMSoP]
I already said this, but to reiterate: I, personally, hear what you
are saying and largely agree with you; however, before we can really
have any kind of discussion on concurrent servers, we HAVE to address
the underlying issues that are missing from PHP. In PHP-srcSo, let's address them...
there are no such things as request objects
This is a non-issue. As has been discussed already, it's perfectly fine to have an event-based system where the event details are an associative array, rather than a rich object.
There are no such things as event loops. There are fibers,
but absolutely no std-library i/o functions are using themThis is what the bulk of Daniil's email is suggesting a way to improve.
We have a long way to go before those will be real things that we can
have a proper conversation about in the context of php-src.If we keep waiting to have the conversation, it will never happen.
And if we start building brand new APIs like infrastructure for worker-mode SAPIs, in ways that are fundamentally incompatible with async, we're just making more work for ourselves when we do get there.
I don't think they are fundamentally incompatible. If we look at
FrankenPHP's implementation, you pass a callback that gets called when
there is a request. This is the same as giving Swoole a callback to
call when a request is received, though the underlying implementation
might be very different. An event-loop doesn't matter here and what
gets passed to the callback is what we are discussing here.
The exact implementation (in php-src) matters, but only so far as what
php-src is capable of. Globals is how this works (atm) and changing
the signature of the callback is generally backwards compatible. For
example, if we start with only globals (no parameters), adding
parameters later once there are request objects/arrays/whatever, is
fine. Calling a callback with parameters when the function actually
doesn't accept any arguments, isn't even a notice
(https://3v4l.org/URj9b).
Changing the underlying implementation in php-src when there are
native fibers/event loops probably won't even change anything (since
that was exactly how they were designed).
But holding up the entire conversation because these things don't even
exist, seems like a pointless endeavor since they may not even pass
the RFC point (IIRC, getting an event-loop in PHP was a large part of
the reason fibers are the way they are because people were against an
event-loop at that time).
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
Robert Landers
Software Engineer
Utrecht NL
Hi,
But holding up the entire conversation because these things don't even
exist,
BTW, I'm not asking to wait for the implementation of a native event loop before implementing a worker mode, I'm asking to design the worker mode API in a way that is compatible with an eventual native event loop (that I hope will eventually get merged into php, as the current status quo of blocking STL I/O is definitely not ideal for a programming language in 2024).
Using superglobals for request information is already using a wrong approach in sight of native async, and while technically this is not completely incompatible with fibers, as superglobals can simply be copied immediately as soon a fiber is started, the same cannot be said for php://output and php://input streams, which cannot be copied in any way.
I ask to completely avoid altering superglobals and other global state such as the php://input/output streams, and instead directly pass/return an array/object (it really does not matter to me if it's a PSR-7 RequestInterface or an array containing _GET, _POST, input, output keys, or anything else as long as superglobals and global state is not touched in any way).
Backwards compatibility for code using superglobals is really a non-issue here, as worker mode in itself is already potentially NOT automatically backwards-compatible with legacy code, since applications running in worler mode have to deal with the fact that the global state is not reset at the end of each request.
This in itself is not an issue as mentioned before, thanks to work inside of major PHP frameworks to better support worker mode, I.e. laravel octane, but since the beneficiaries of worker mode will mostly be modern frameworks and most legacy code will already require changes to better support it, using a new API with a standalone request object/array instead of superglobals is not a real issue.
Regards,
Daniil Gentili.
I don't think they are fundamentally incompatible. If we look at
FrankenPHP's implementation, you pass a callback that gets called when
there is a request.
No, you pass a callback which is called exactly once, for the next request. You have to implement your own loop if you want to handle multiple requests, which obviously isn't how it would work with an async event loop.
That was one of my suggested changes: move the loop into C, so that the API was "callback called for each request". This actually adds flexibility on the server end, to decide how often to call that callback, do so asynchronously, etc.
Globals is how this works (atm)
It's how it works for native SAPIs. It's not, as far as I know, how any worker system other than FrankenPHP has implemented its API. Every other implementation I've seen, whether async or not, passes in some form of request data to the callback, with the exception of RoadRunner, which gives the data as a return value from a "get next request" function.
So, the second suggested change is to standardise on the most common pattern of passing parameters to a callback, rather than the unusual one of populating and clearing superglobals. As a bonus, this pattern works with both non-async and async workers.
changing the signature of the callback is generally backwards compatible
This is true in the narrow sense that it won't cause any fatal errors. But if you write your application assuming that it will run in an environment where globals are populated for you, it will not run in an environment which no longer populates those globals.
Changing the underlying implementation in php-src when there are
native fibers/event loops probably won't even change anything (since
that was exactly how they were designed).
Sounds great! So we don't need to wait to put that implementation in place then.
But holding up the entire conversation ...
There is no reason whatsoever to hold anything up. The suggestion is not "don't implement any worker API until we have an async implementation", it's "a worker API sounds great, let's implement one that looks like this".
Yes, it might take slightly longer to define some new array structures, but we're talking about a few hours work to give us a much more flexible system, not weeks of complex engineering.
If the proposal is "copy some code from FrankenPHP into php-src, which nobody else will want to use", it's pointless; if it's "standardise an API with some enabling code", then of course we want to spend a bit of time designing that API.
Regards,
--
Rowan Tommins
[IMSoP]
I don't think they are fundamentally incompatible. If we look at
FrankenPHP's implementation, you pass a callback that gets called when
there is a request.No, you pass a callback which is called exactly once, for the next request. You have to implement your own loop if you want to handle multiple requests, which obviously isn't how it would work with an async event loop.
That was one of my suggested changes: move the loop into C, so that the API was "callback called for each request". This actually adds flexibility on the server end, to decide how often to call that callback, do so asynchronously, etc.
I think the goal here is to provide the basic building block: a
function that takes a callable that, when called, blocks the script.
Even when you have an event loop, there is some point when you call
something and enter an infinite loop (the event loop), and no more of
that file will be called ($app->start() or whatever). This is that
function for all intents and purposes. You can implement your own
event-loop using do/while (such as in FrankenPHP), or a SAPI can call
it in a loop for you. The bedrock is in core PHP, providing a
standardized way of setting this up ... obviously, there are also
SAPIs out there doing their own thing, and there always will be.
Globals is how this works (atm)
It's how it works for native SAPIs. It's not, as far as I know, how any worker system other than FrankenPHP has implemented its API. Every other implementation I've seen, whether async or not, passes in some form of request data to the callback, with the exception of RoadRunner, which gives the data as a return value from a "get next request" function.
Nearly every library in existence knows how to use these globals
(including 30 years old legacy code). There are also the unwieldy PSR
request/response containers for which there are dozens (if not
hundreds) of implementations. It would be fantastic if there were
already an extension-based implementation that could be adopted into
php-src; though I feel like that is a separate conversation.
So, the second suggested change is to standardise on the most common pattern of passing parameters to a callback, rather than the unusual one of populating and clearing superglobals. As a bonus, this pattern works with both non-async and async workers.
changing the signature of the callback is generally backwards compatible
This is true in the narrow sense that it won't cause any fatal errors. But if you write your application assuming that it will run in an environment where globals are populated for you, it will not run in an environment which no longer populates those globals.
This is easy to handle from C. If the callback takes an argument,
don't fill in the super-globals. It allows legacy apps to be slowly
"upgraded" while allowing newer apps to take full advantage of a SAPI.
That's how I would implement it, anyway. There is also something to be
said to go "all the way" and just abandoning legacy apps, but that
doesn't feel like something PHP would do.
Changing the underlying implementation in php-src when there are
native fibers/event loops probably won't even change anything (since
that was exactly how they were designed).Sounds great! So we don't need to wait to put that implementation in place then.
But holding up the entire conversation ...
There is no reason whatsoever to hold anything up. The suggestion is not "don't implement any worker API until we have an async implementation", it's "a worker API sounds great, let's implement one that looks like this".
Yes, it might take slightly longer to define some new array structures, but we're talking about a few hours work to give us a much more flexible system, not weeks of complex engineering.
If the proposal is "copy some code from FrankenPHP into php-src, which nobody else will want to use", it's pointless; if it's "standardise an API with some enabling code", then of course we want to spend a bit of time designing that API.
That's fair. I was taking this push-back from you and others as, "No,
we don't want this unless we can have all these other things," so
thank you for clarifying that. I can largely agree -- I use amphp +
fibers extensively in another project, so seeing more love for
concurrent servers would be nice. Maybe I saw it that way because I
have a fairly deep understanding of the shortcomings with
fibers/async-php and see the amount of work required to support what
you are proposing. However, if we go into the design with the
concurrent server story in mind, I think we can create something much
better than what is available from FrankenPHP.
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
pt., 5 sty 2024 o 13:19 Robert Landers landers.robert@gmail.com
napisał(a):
On Fri, Jan 5, 2024 at 11:59 AM Rowan Tommins rowan.collins@gmail.com
wrote:Globals is how this works (atm)
It's how it works for native SAPIs. It's not, as far as I know, how any
worker system other than FrankenPHP has implemented its API. Every other
implementation I've seen, whether async or not, passes in some form of
request data to the callback, with the exception of RoadRunner, which gives
the data as a return value from a "get next request" function.Nearly every library in existence knows how to use these globals
(including 30 years old legacy code). There are also the unwieldy PSR
request/response containers for which there are dozens (if not
hundreds) of implementations. It would be fantastic if there were
already an extension-based implementation that could be adopted into
php-src; though I feel like that is a separate conversation.
There are indeed dozens of libraries already working with PSR nicely but
IMHO
the API should provide all the necessary information in a way that allows
the construction of such objects,
but suggesting PSR with request/response objects will limit the
capabilities of worker mode API
to handle pure HTTP protocol only.
What I'd like to say is that I believe for the initial proposal of any
eventual worker mode API
with the PSR with request/response objects should not be considered at all.
Cheers
On Fri, Jan 5, 2024 at 9:40 AM Michał Marcin Brzuchalski <
michal.brzuchalski@gmail.com> wrote:
There are indeed dozens of libraries already working with PSR nicely but
IMHO
the API should provide all the necessary information in a way that allows
the construction of such objects,
but suggesting PSR with request/response objects will limit the
capabilities of worker mode API
to handle pure HTTP protocol only.What I'd like to say is that I believe for the initial proposal of any
eventual worker mode API
with the PSR with request/response objects should not be considered at all.Cheers
I think it's been mentioned quite a few times that it doesn't matter what
gets passed to the callable function that hands over control to userland,
as long as it's more functional-style and not superglobals. I also think
that there's merit in both sides of the conversation between PSR-7 vs
associative arrays, but I find it more important to get one of them, any of
them and not halt for one or the other. PSR would make it HTTP-only, yes,
but that largely benefits the PHP ecosystem to an extent order of magnitude
larger than any non-HTTP format. On the other hand, being a dynamically
typed language, nothing holds us from having a more simple/generic
function handler(mixed $event)
format which can also be used to process
HTTP and non-HTTP events.
I do prefer the more generic one as I would be interested in seeing what
PHP would become with the capability of processing non-HTTP protocols made
easier. That being said, I find it important to consider the quality of
such an API for its users. It would end up forcing users to do the
following:
function handler(mixed $event) {
if (isset($event['_REQUEST'])) {
// We are on HTTP Protocol
}
if (isset($event['...'])) {
// This is a Websocket
}
}
If the original proposal is correct and/or my little understanding of this
thread is somewhat in the right direction, it means that the introduction
of a PHP function that asks its engine for the next event to process isn't
a huge amount of work on PHP Internals. If that's true, I would ask that we
consider making something more flexible / forgiving of errors / adjustable
and evolution-friendly. Instead of striving so much for coming up with one
perfect API that shall be the one true API for PHP for the next decade, we
can instead consider the possibility of having multiple small APIs that can
harmonically coexist. Example:
Classic:
$classicHttpHandler = function (array $get, array $post, array $request,
array $server, array $cookies): string|Stringable {
// process incoming request in a somewhat backward-compatible friendly
way
return 'http response'; // Return the response similarly to how we used
to `echo` it to the server.
}
worker_http_classic($classicHttpHandler);
PSR-7:
$httpMiddlewareHandler = function (\Psr\Http\Message\RequestInterface
$request): \Psr\Http\Message\ResponseInterface {
// Process Request
}
worker_http_psr7($httpMiddlewareHandler);
HTTP Raw
$httpHandler = function (\Psr\Http\Message\StreamInterface $raw):
\Psr\Http\Message\ResponseInterface {
// Process Request
}
worker_http_raw($httpHandler);
STDIN
$stdinHandler = function (SomeStdinCompatibleType $stdin):
SomeStdoutCompatibleType {
}
worker_stdin($stdinHandler);
Websocket
$websocketHandler = function (string $event, mixed $data): ??? {
// Process websocket incoming event
}
worker_websocket_event($httpHandler);
These APIs don't need to be ready on day one. They don't even need to be
ready at all, actually. Each would end up being its own RFC. What makes the
system a bit more. flexible is the api naming which follows the pattern PHP
Engine Namespace (worker), Purpose Namespace (_http, _websocket, etc) and
Variation Namespace (_classic, _psr7, _raw).
For me personally one awesome last step would make this a PHP Class instead
of procedural functions. That would be even better because we could use the
Class namespace itself to version it and provide future changes without
breaking what's been introduced already.
--
Marco Deleu
This is easy to handle from C. If the callback takes an argument,
don't fill in the super-globals.
Again, that's compatible only in a narrow sense: it provides both APIs on any run-time which can do so safely.
You still have an incompatible upgrade to make though: if you write code today for FrankenPHP, and directly use the super-global arrays it populates, you cannot take that code tomorrow and use it in Swoole, which does not provide those super-globals.
If you write code today which uses callback parameters, you can take that code and use it unmodified with any system which provides those parameters - including async implementations. All that's missing for that to happen right now is a standard format for those parameters.
It allows legacy apps to be slowly
"upgraded" while allowing newer apps to take full advantage of a SAPI.
It's actually quite easy to add most of the backwards compatibility needed for legacy apps in userland, by populating the superglobals, and running an output buffer to capture echo etc into the response.
However, if we go into the design with the
concurrent server story in mind, I think we can create something much
better than what is available from FrankenPHP.
Precisely. That's why I used the phrase "forwards compatibility" - I'm not saying php-src needs to support all of this right now, just that the API design should have an eye on the future, not just the past.
Regards,
--
Rowan Tommins
[IMSoP]