Commit 0c6db8ccfdc for php

commit 0c6db8ccfdc207398e9724c36824abae1d6ca4f2
Author: Jakub Zelenka <bukka@php.net>
Date:   Tue Sep 29 21:17:39 2026 +0200

    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.

diff --git a/main/poll/poll_backend_epoll.c b/main/poll/poll_backend_epoll.c
index 1cc05b3005e..85660a9322f 100644
--- a/main/poll/poll_backend_epoll.c
+++ b/main/poll/poll_backend_epoll.c
@@ -18,6 +18,11 @@

 #include <sys/epoll.h>

+#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;
@@ -186,13 +191,21 @@ static int epoll_backend_wait(
 		backend_data->events_capacity = max_events;
 	}

-	int nfds;
+	int nfds = 0;
 #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++) {