Add an event loop driver on the Io\Poll API of PHP 8.6 - #128
nicolas-grekas wants to merge 1 commit 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
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()before Fix use-after-free in StreamPollHandle when its stream is closed php/php-src#23804, so the driver drops the watcher without removing it when the resource is already gone. The descriptor leaves the poll set on close anyway.