Add an event loop driver on the Io\Poll API of PHP 8.6 - #128
nicolas-grekas wants to merge 2 commits into
Conversation
4080036 to
29acf36
Compare
|
Possible follow up, out of scope here: letting EventLoop::onReadable($handle->getStream(), function () use ($handle) { ... });
|
|
Benched against the extension drivers on 8.6, 20k socket round trips with N idle watched sockets alongside, best of 3:
Repeating a cell five times swings by 9 to 18% here, so io_poll, ev, event and uv are the same speed as far as this machine can tell, and Worth knowing while comparing them: ev and uv don't build on 8.6 at all right now, The handle support I floated in the comment above is parked for now: php-src would rather keep that part of the API internal for 8.6 (php/php-src#23810), and 8.7 is getting handle types with no descriptor at all, so it is worth revisiting once those settle. |
IoPollDriver watches streams through Io\Poll, which is epoll on Linux, kqueue on BSD and macOS and event ports on Solaris, so the loop is no longer capped at the FD_SETSIZE of stream_select() and no longer pays its O(n) scan per tick. The driver is picked over StreamSelectDriver when the API is available, natively on PHP 8.6 and through symfony/polyfill-io-poll below it.
29acf36 to
712ac54
Compare
kelunik
left a comment
There was a problem hiding this comment.
Thanks! This looks like solid work. I left a few (minor) comments.
|
Thanks for the review, comments addressed in the second commit. |
601e389 to
ae03b48
Compare
getTimeout() returns null rather than -1 when no timer is pending, the cancel-after-close test moves to the shared suite since every driver passes it, the signal test now blocks the loop in wait() and interrupts it with pcntl_alarm() so it exercises ERROR_INTERRUPTED for real, Windows is detected in one place, and the polyfill is no longer suggested since it is slower than stream_select().
ae03b48 to
c286c43
Compare
Fix #125
PHP 8.6 ships the Io\Poll API, which is epoll on Linux, kqueue on BSD and macOS and event ports on Solaris.
IoPollDriverwatches streams through it, so the loop is no longer capped byFD_SETSIZEand no longer pays the O(n) scan ofstream_select()on every tick.20k socket round trips, with N idle watched sockets alongside, on 8.6, best of three:
It sits after the uv, ev and event drivers in
DriverFactory, since those are explicit installs and cover more (signals, child processes), and beforeStreamSelectDriver. Signals still go through pcntl here, the API has no signal handles yet.The driver also runs below 8.6 through
symfony/polyfill-io-poll, but that polyfill is backed bystream_select()itself, so it is 10 to 35% slower than callingstream_select()directly.isSupported()returns false there and the factory keeps choosingStreamSelectDriver;REVOLT_DRIVERstill lets anyone opt in, and the tests run on that path too. Its CI leg installs the polyfill 1.x branch because the driver needs symfony/polyfill#655 and #656, merged but not released yet, so it can be pinned to a tag once that ships.Three behaviours of the API the driver has to absorb, each with a test:
php://tempand/dev/null, soonReadable(STDIN)fails wherever stdin is redirected, CI included. Refused handles are retried on aBackend::Pollcontext and treated as always ready, which is whatselect()reports for them.wait()throwsERROR_INTERRUPTEDwhen a signal arrives, the waystream_select()returns false on EINTR. The driver swallows it and dispatches the signal on the next tick.Watcher::remove()used to crash on a closed stream, ext/standard: Fix use-after-free when a polled stream is closed php/php-src#23791 fixed that for 8.6.0RC1, so the 8.6 pre-releases before it crash there instead.