As presented, this RFC proposes two optional module hooks, available
for extensions (no userland change), which are called between requests
served by a "worker" script.
thanks for working on this. It would be really nice, from an extension
perspective, to have an explicit way to detect the logical request
boundaries in an explicit way.
I had one question when looking at the FrankenPHP example at
It seems that the lifecycle is actually starting with REND and
finishing with RBEGIN? Is that intended, I can see why it is the way
it is, it just feels odd.
So for example:
<?php
$handler = static function () {
echo "request handled";
};
while (frankenphp_handle_request($handler)) {}
?>
The lifecycle of this code would look like:
- RINIT (plus all the things before)
- FrankenPHP would execute till the call to frankenphp_handle_request()
- then REND (to "shutdown"/end the dummy bootstrap request)
- wait for an actual HTTP request and then we enter the RBEGIN -> REND
loop as long as requests are served to that worker
- if that worker is about to shut down, frankenphp_handle_request()
would return false, but also do another call to RBEGIN, just none to
REND anymore before RSHUTDOWN
Kind regards
Florian
On Mon, Nov 9, 2026 at 3:25 PM Kévin Dunglas <kevin@dunglas.fr> wrote:
As presented, this RFC proposes two optional module hooks, available
for extensions (no userland change), which are called between requests
served by a "worker" script.
Thanks for taking the time to read the RFC and review the patches.
Yes, it’s intended, and it’s already how FrankenPHP behaves today.
Because of how the engine works internally, the "dummy" request that
boots the worker script and the "logical" requests handled in the loop
don’t overlap: they run one after the other.
The full sequence in FrankenPHP is:
* RINIT ("dummy" request)
* userland bootstrap code
* main loop, frankenphp_handle_request() waits for a request (its
first call first runs REND to clean the request-specific state tied to
the "dummy" request):
* RBEGIN (logical HTTP request)
* callback
* REND (logical HTTP request)
* when the worker must stop, frankenphp_handle_request() calls
RBEGIN and returns false: this recreates a "dummy" request, because
the code after the loop can still write output, and
php_request_shutdown() needs an active request
* userland shutdown code
* RSHUTDOWN ("dummy" request)
For extensions, the rules are the same as today: what is set up in
RINIT or RBEGIN must be released in the next REND or RSHUTDOWN. A
debugger or a profiler will likely keep running the same code as today
in RSHUTDOWN, and an adapted version of it in the new REND. That’s by
design: existing extensions keep working unchanged, and a logical
request is now simply one that starts with RBEGIN and ends with REND.
Best,