From a5b5008a5ee33631d55edf141ab674e0b8441393 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 2 Nov 2026 13:07:46 -0500 Subject: [PATCH] sapi/fpm: Preserve poll event indexes after worker replacement When the next poll slot was occupied, fpm_event_poll_add() registered the fd in the first free slot found by the search but stored the occupied index in the event. fpm_event_poll_wait() then compared the event fd against the wrong pollfd entry and skipped the event, so with catch_workers_output the master did not read a replacement worker's output while it ran. Store the slot actually used and advance the hint from it. --- NEWS | 4 + sapi/fpm/fpm/events/poll.c | 3 +- .../tests/events-poll-worker-replacement.phpt | 80 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 sapi/fpm/tests/events-poll-worker-replacement.phpt diff --git a/NEWS b/NEWS index 26abaf2f03ab..b28af4254d60 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,10 @@ PHP NEWS - Curl: . Fixed curl_reset() retaining CURLOPT_PRIVATE after reset. (lacatoire) +- FPM: + . Fixed worker output not being read after worker replacement with the + poll event mechanism. (Ilia Alshanetsky) + - MBString: . Fixed stack overflow in mb_check_encoding() and mb_convert_encoding() with deeply nested arrays. (Eyüp Can Akman) diff --git a/sapi/fpm/fpm/events/poll.c b/sapi/fpm/fpm/events/poll.c index 30ebbc785d94..809dd0296eea 100644 --- a/sapi/fpm/fpm/events/poll.c +++ b/sapi/fpm/fpm/events/poll.c @@ -206,7 +206,8 @@ static int fpm_event_poll_add(struct fpm_event_s *ev) /* {{{ */ pollfds[i].events = POLLIN; /* remember the event place in the fd list and suppose next slot is free */ - ev->index = next_free_slot++; + ev->index = i; + next_free_slot = i + 1; if (next_free_slot >= npollfds) { next_free_slot = 0; } diff --git a/sapi/fpm/tests/events-poll-worker-replacement.phpt b/sapi/fpm/tests/events-poll-worker-replacement.phpt new file mode 100644 index 000000000000..29eade27593e --- /dev/null +++ b/sapi/fpm/tests/events-poll-worker-replacement.phpt @@ -0,0 +1,80 @@ +--TEST-- +FPM: poll continues capturing worker output after worker replacement +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$pid = (int) $tester->request('message=before&pid=1', address: '{{ADDR[first]}}')->getBody(); +if ($pid <= 0) { + $tester->terminate(); + $tester->close(); + die("Missing worker PID\n"); +} +$tester->expectLogLine('before', decorated: false); +$tester->request('message=barrier', address: '{{ADDR[second]}}')->expectBody('ok'); +$tester->expectLogLine('barrier', decorated: false); +$tester->signal('KILL', $pid); +$tester->expectLogWarning('child \\d+ exited on signal 9 \\(SIGKILL\\) after \\d+\\.\\d+ seconds from start', 'first'); +$tester->expectLogNotice('child \\d+ started', 'first'); +$tester->request('message=after', address: '{{ADDR[first]}}')->expectBody('ok'); +$tester->expectLogLine('after', decorated: false); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); +fclose($lock); +?> +Done +--EXPECT-- +Done +--CLEAN-- +