Skip to content

Commit 6972612

Browse files
committed
Improve fix for GH-16889
The original patch[1] cared only about pipe handles in the rset, but would be problematic if there are other handles (e.g. files in the rset, or pipes/files in the other sets), because `php_select()` would return immediately, reporting all non read-pipe handles as ready, but possibly never reporting read-pipe handles. We fix this by applying different logic for the case where only pipe handles are supplied in the rset, but no handles in the wset or eset. In this case `php_select()` only returns when actually one of the handles is ready, or when the timeout expires. To avoid busy looping in this case, we sleep for a short amount of time. This matches POSIX behavior. In all other cases, `php_select()` behaves as before (i.e. prior to the original fix), that is it returns immediately, reporting all handles as ready. We also add a test case that demonstrates multiplexing the output of a couple of child processes. See also the discussion on <#16917>. [1] <b614b4a> Closes GH-17174.
1 parent fadceca commit 6972612

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

UPGRADING

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ PHP 8.5 UPGRADE NOTES
189189
and FFI::load(). However, this convenience feature should not be used in
190190
production.
191191

192+
* Streams:
193+
. If only pipe streams are contained in the $read array, and the $write and
194+
$except arrays are empty, stream_select() now behaves similar to POSIX
195+
systems, i.e. the function only returns if at least one pipe is ready to be
196+
read, or after the timeout expires. Previously, stream_select() returned
197+
immediately, reporting all streams as ready to read.
198+
192199
========================================
193200
13. Other Changes
194201
========================================
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Multiplexing of child output
3+
--FILE--
4+
<?php
5+
$php = getenv("TEST_PHP_EXECUTABLE");
6+
$desc = [
7+
["null"],
8+
["pipe", "w"],
9+
["null"]
10+
];
11+
$read_pipes = [];
12+
for ($i = 0; $i < 10; $i++) {
13+
$procs[] = proc_open([$php, "-r", "usleep(100000 * (10 - $i)); echo 'hello$i';"], $desc, $pipes);
14+
$read_pipes[] = $pipes[1];
15+
}
16+
$rset = $read_pipes;
17+
$wset = null;
18+
$eset = null;
19+
while (!empty($read_pipes) && stream_select($rset, $wset, $eset, 2) > 0) {
20+
foreach ($rset as $pipe) {
21+
echo fread($pipe, 6), "\n";
22+
unset($read_pipes[array_search($pipe, $read_pipes)]);
23+
}
24+
$rset = $read_pipes;
25+
}
26+
?>
27+
--EXPECT--
28+
hello9
29+
hello8
30+
hello7
31+
hello6
32+
hello5
33+
hello4
34+
hello3
35+
hello2
36+
hello1
37+
hello0

win32/select.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
#include "php.h"
1818
#include "php_network.h"
19+
#include "win32/time.h"
1920

2021
/* Win32 select() will only work with sockets, so we roll our own implementation here.
2122
* - If you supply only sockets, this simply passes through to winsock select().
2223
* - If you supply file handles, there is no way to distinguish between
2324
* ready for read/write or OOB, so any set in which the handle is found will
24-
* be marked as ready. Pipes will be checked if they are ready for read, though.
25+
* be marked as ready.
26+
* - If you supply only pipe handles in rfds, and no handles in wfds or efds,
27+
* the pipes will only be marked as ready if there is data available.
2528
* - If you supply a mixture of handles and sockets, the system will interleave
2629
* calls between select() and WaitForMultipleObjects(). The time slicing may
2730
* cause this function call to take up to 100 ms longer than you specified.
@@ -34,6 +37,7 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e
3437
HANDLE handles[MAXIMUM_WAIT_OBJECTS];
3538
int handle_slot_to_fd[MAXIMUM_WAIT_OBJECTS];
3639
int n_handles = 0, i;
40+
int num_read_pipes = 0;
3741
fd_set sock_read, sock_write, sock_except;
3842
fd_set aread, awrite, aexcept;
3943
int sock_max_fd = -1;
@@ -78,6 +82,9 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e
7882
sock_max_fd = i;
7983
}
8084
} else {
85+
if (SAFE_FD_ISSET(i, rfds) && GetFileType(handles[n_handles]) == FILE_TYPE_PIPE) {
86+
num_read_pipes++;
87+
}
8188
handle_slot_to_fd[n_handles] = i;
8289
n_handles++;
8390
}
@@ -136,7 +143,7 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e
136143
if (WAIT_OBJECT_0 == WaitForSingleObject(handles[i], 0)) {
137144
if (SAFE_FD_ISSET(handle_slot_to_fd[i], rfds)) {
138145
DWORD avail_read = 0;
139-
if (GetFileType(handles[i]) != FILE_TYPE_PIPE
146+
if (num_read_pipes < n_handles
140147
|| !PeekNamedPipe(handles[i], NULL, 0, NULL, &avail_read, NULL)
141148
|| avail_read > 0
142149
) {
@@ -156,6 +163,9 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e
156163
}
157164
}
158165
}
166+
if (retcode == 0 && num_read_pipes == n_handles && sock_max_fd < 0) {
167+
usleep(100);
168+
}
159169
} while (retcode == 0 && (ms_total == INFINITE || GetTickCount64() < limit));
160170

161171
if (rfds) {

0 commit comments

Comments
 (0)