From 40a6e7e2eff908a6bcb6c682c206289120d48442 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 20 Sep 2026 17:00:32 +0200 Subject: [PATCH] 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. --- .../poll/poll_wait_timeout_overflow.phpt | 29 +++++++++++++++++++ main/poll/php_poll_internal.h | 6 ++++ 2 files changed, 35 insertions(+) create mode 100644 ext/standard/tests/poll/poll_wait_timeout_overflow.phpt diff --git a/ext/standard/tests/poll/poll_wait_timeout_overflow.phpt b/ext/standard/tests/poll/poll_wait_timeout_overflow.phpt new file mode 100644 index 000000000000..5009d3edd8ce --- /dev/null +++ b/ext/standard/tests/poll/poll_wait_timeout_overflow.phpt @@ -0,0 +1,29 @@ +--TEST-- +Io\Poll: a timeout that overflows an int of milliseconds keeps waiting +--SKIPIF-- +isAvailable()) { + die("skip poll backend not available\n"); +} +if (PHP_OS_FAMILY === 'Windows') { + die("skip POSIX only\n"); +} +?> +--FILE-- + ['pipe', 'w']], $pipes); + +$poll_ctx = new Io\Poll\Context(Io\Poll\Backend::Poll); +$watcher = $poll_ctx->add(new StreamPollHandle($pipes[1]), [Io\Poll\Event::Read]); + +// 158913790 seconds is about 5 years, and wraps around to 48ms as an int of milliseconds +$events = $poll_ctx->wait(Time\Duration::fromSeconds(158913790)); + +var_dump(count($events), $events[0] === $watcher); + +fclose($pipes[1]); +proc_close($process); +?> +--EXPECT-- +int(1) +bool(true) diff --git a/main/poll/php_poll_internal.h b/main/poll/php_poll_internal.h index 951521314393..21b85575409d 100644 --- a/main/poll/php_poll_internal.h +++ b/main/poll/php_poll_internal.h @@ -164,6 +164,12 @@ static inline int php_poll_timespec_to_ms(const struct timespec *timeout) return -1; } + /* Cap rather than wrap around: a timeout that long is as good as indefinite, + * where truncating it to an int made poll() return after a few milliseconds */ + if (timeout->tv_sec >= (INT_MAX - 1000) / 1000) { + return INT_MAX; + } + int ms = (int) (timeout->tv_sec * 1000); /* Round nanoseconds up to the next millisecond to avoid premature return */ if (timeout->tv_nsec > 0) {