12 Commits
Author SHA1 Message Date
Jakub Zelenka a85fcf30ab Use C11 atomics for epoll_pwait2_available 2026-09-29 21:45:58 +02:00
Jakub Zelenka 0c6db8ccfd Fall back to epoll_wait when epoll_pwait2 is unavailable at runtime (#23825)
HAVE_EPOLL_PWAIT2 only tells whether the libc exports the wrapper, which
glibc does since 2.35 regardless of the running kernel. A PHP built on a
kernel with epoll_pwait2 and run on one older than 5.11 gets ENOSYS from
every Context::wait() call, which makes the epoll backend and thus the
Auto backend unusable. The same happens under emulation layers that do
not implement the syscall.

Try epoll_pwait2 first and on ENOSYS or ENOTSUP switch the process to
epoll_wait with a millisecond timeout, retrying the current call so the
failure is never visible to the caller. The flag is process wide since
kernel support is the same for every thread, and it is atomic so the
first concurrent waits in a ZTS build do not race on it.
2026-09-29 21:17:39 +02:00
Nicolas Grekas 7e5029bd37 Map epoll_ctl() errors through php_poll_errno_to_error() (#23805)
The epoll backend reported everything but EEXIST and ENOENT as ERR_SYSTEM, so
a caller could not tell that epoll refuses the handle from a real failure. The
kqueue backend already maps errno, this makes epoll do the same, with EPERM
reported as ERR_NOSUPPORT: that is what epoll_ctl() returns for files that do
not implement polling, such as regular files and /dev/null.
2026-09-21 17:16:21 +02:00
Jakub Zelenka 04066f5296 Fix GH-22844: Io\Poll watchers after the watched stream is closed (#23822)
A StreamPollHandle keeps a reference to the stream resource, but an
explicit fclose() still closes the stream underneath the watcher. The
watcher could then no longer resolve its fd, so its backend registration
was left behind: a recycled fd number confused the registry and the poll
backend, and on epoll a duplicated fd could keep the interest alive and
hand a freed watcher back from wait().

Streams now keep a list of their watchers and notify them from
php_stream_free() before the fd is closed, so every watcher is
unregistered while the fd is still valid. The Context registry is keyed
by the registered fd, which the watcher caches, so remove() no longer
depends on the stream and an fd number reused after a close is detected
on add(). A watcher retired by the close reports inactive and its
remove() becomes a no-op. modifyEvents() re-adds a fired one-shot
registration that the backend dropped, and the kqueue backend drops its
tracking entry when there is nothing left to delete.

This follows the approach of GH-22848 by Ilia Alshanetsky.
2026-09-21 16:13:21 +02:00
Nicolas Grekas 39f98f768a ext/standard: Fix Io\Poll timeouts longer than INT_MAX milliseconds
php_poll_timespec_to_ms() cast seconds * 1000 to an int, so a wait() of more than
about 24 days wrapped around: five years came out as 48ms on the poll backend.
The value is capped now. epoll is only affected where it falls back to
epoll_wait(), since epoll_pwait2() takes the timespec as is.
2026-09-21 12:44:15 +02:00
Gina Peter Banyard 55183b9d12 poll: fetch method ptr directly (#22816)
Rather than going through a whole callability check via call_user_function() we can grab the known method pointer and call it directly.

This removes some allocations
2026-07-20 14:32:29 +01:00
Jakub Zelenka c6c8db5a00 main/poll: Fix kqueue event buffer overflow in grouped mode (#22606)
Cap each kevent() request at the number of entries that can still be
delivered instead of requesting twice the buffer size. Events that are
not retrieved (including oneshot and edge-triggered ones) stay pending
in the kqueue and are delivered by the next wait, matching the epoll
behavior. Merged read+write events can under-fill the buffer, so top up
with zero timeout rounds, and base the suitable max events on the
filter count so a default sized wait retrieves everything in one call.
2026-07-12 20:20:53 +02:00
Ilia Alshanetsky 2ddbc0a672 main/poll: Record wait() error on every backend (#22326)
php_poll_wait() reported a stale error code when a backend's wait syscall
failed without recording one: epoll, poll and kqueue returned -1 without
setting it. Record it from errno in those backends so php_poll_get_error()
reflects the actual failure. eventport and wsapoll already did, the
latter from WSAGetLastError().

Closes GH-22326
2026-07-05 09:17:38 +02:00
Ilija Tovilo a3ef478bd1 Move decls into ZEND_HASH_FOREACH macros in poll API 2026-06-20 15:15:34 +02:00
Ilija Tovilo 6e989d23d5 Name all unnamed structs in main/poll 2026-06-18 20:05:49 +02:00
Ilia Alshanetsky fc84d16595 Fix Io\Poll memory-safety issues
Several memory-safety issues in the new Io\Poll API, found by review and
confirmed under valgrind:

- Watcher kept a raw pointer to its Context's php_poll_ctx with no
  reference, so dropping the Context while holding a Watcher left
  remove()/modify() dereferencing freed memory (use-after-free). The
  Context now neutralizes its watchers (active=false, poll_ctx=NULL)
  before it is destroyed, so those calls throw InactiveWatcherException.
- StreamPollHandle took a reference on the stream resource in the
  constructor but never released it, leaking the descriptor for the
  rest of the request. Store the zend_resource and release it in the
  handle cleanup; the php_stream may already be freed by then (e.g.
  the user closed it), so the cleanup must not dereference it.
- Watcher and Context had no get_gc handler, so reference cycles through
  Watcher::$data were uncollectable. Add get_gc for both.
- Context, Watcher and StreamPollHandle were cloneable through the
  default handler, which shallow-copied the backing php_poll_ctx and the
  watcher map by pointer and double-freed them on destruction. Mark all
  three uncloneable.
- Calling __construct() a second time on a Context or StreamPollHandle
  replaced the backing context or handle data without releasing the
  first, leaking it. Throw if the object is already constructed.
- The add(), modify(), remove() and wait() entry points accepted a NULL
  ctx and forwarded it to php_poll_set_error(), which dereferenced it.
  The userland layer already gates on an active context before reaching
  the C API, so assert a non-NULL ctx in those entry points instead.

Closes GH-22316
2026-06-18 12:30:59 -04:00
Jakub Zelenka 2d151087a7 Add internal polling API
This introduces various IO polling backend including epoll, kqueue,
event ports, poll and WSAPoll. It makes the usage compatible between
backends.
2026-06-14 19:45:04 +02:00