From 04d1b82b576ff52148a00e77a3fa0957a4f6bd58 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 21 Sep 2026 17:44:16 +0200 Subject: [PATCH] Fall back to epoll_wait when epoll_pwait2 is unavailable at runtime 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. --- main/poll/poll_backend_epoll.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/main/poll/poll_backend_epoll.c b/main/poll/poll_backend_epoll.c index 394b0dc52446..15cf6f2f0c1c 100644 --- a/main/poll/poll_backend_epoll.c +++ b/main/poll/poll_backend_epoll.c @@ -18,6 +18,11 @@ #include +#ifdef HAVE_EPOLL_PWAIT2 +/* Cleared when the running kernel returns ENOSYS */ +static zend_atomic_bool epoll_pwait2_available = ZEND_ATOMIC_BOOL_INITIALIZER(true); +#endif + typedef struct epoll_backend_data { int epoll_fd; struct epoll_event *events; @@ -182,11 +187,19 @@ static int epoll_backend_wait( int nfds; #ifdef HAVE_EPOLL_PWAIT2 - nfds = epoll_pwait2(backend_data->epoll_fd, backend_data->events, max_events, timeout, NULL); -#else - int timeout_ms = php_poll_timespec_to_ms(timeout); - nfds = epoll_wait(backend_data->epoll_fd, backend_data->events, max_events, timeout_ms); + if (EXPECTED(zend_atomic_bool_load_ex(&epoll_pwait2_available))) { + nfds = epoll_pwait2( + backend_data->epoll_fd, backend_data->events, max_events, timeout, NULL); + if (UNEXPECTED(nfds < 0 && (errno == ENOSYS || errno == ENOTSUP))) { + zend_atomic_bool_store_ex(&epoll_pwait2_available, false); + } + } + if (UNEXPECTED(!zend_atomic_bool_load_ex(&epoll_pwait2_available))) #endif + { + int timeout_ms = php_poll_timespec_to_ms(timeout); + nfds = epoll_wait(backend_data->epoll_fd, backend_data->events, max_events, timeout_ms); + } if (nfds > 0) { for (int i = 0; i < nfds; i++) {