Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion sapi/fpm/fpm/events/poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
80 changes: 80 additions & 0 deletions sapi/fpm/tests/events-poll-worker-replacement.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--TEST--
FPM: poll continues capturing worker output after worker replacement
--SKIPIF--
<?php
include "skipif.inc";
if (PHP_OS_FAMILY !== 'Linux') {
die('skip requires Linux poll EOF semantics for slot reuse (see php/php-src#25154)');
}
?>
--FILE--
<?php
require_once "tester.inc";

$cfg = <<<'EOT'
[global]
error_log = {{FILE:LOG}}
events.mechanism = poll
[first]
listen = {{ADDR[first]}}
pm = static
pm.max_children = 1
catch_workers_output = yes
decorate_workers_output = no
[second]
listen = {{ADDR[second]}}
pm = static
pm.max_children = 1
catch_workers_output = yes
decorate_workers_output = no
EOT;

$code = <<<'EOT'
<?php
if (isset($_GET['message'])) {
file_put_contents('php://stderr', $_GET['message'] . "\n");
}
echo isset($_GET['pid']) ? getmypid() : 'ok';
fastcgi_finish_request();
$lock = fopen(__DIR__ . '/events-poll-worker-replacement.lock', 'r+');
if (!$lock || !flock($lock, LOCK_EX)) {
throw new RuntimeException('Could not wait on worker barrier');
}
EOT;

$path = __DIR__ . '/events-poll-worker-replacement.lock';
$lock = fopen($path, 'c+');
if (!$lock || !chmod($path, 0666) || !flock($lock, LOCK_EX)) {
die("Could not lock worker barrier\n");
}
$tester = new FPM\Tester($cfg, $code);
$tester->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--
<?php
require_once "tester.inc";
FPM\Tester::clean();
@unlink(__DIR__ . '/events-poll-worker-replacement.lock');
?>
Loading