You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
stream_select() timeout useless for pipes on Windows #16889
I may be wrong on the specifics but I do know those tests got stuck for way longer than 32 seconds. I ended up having to abort them after about half an hour or so. Very suboptimal either way lol
With the default scheduler frequency on Windows (assuming usleep doesn't spin-wait; I didn't look at the impl) this could sleep for as long as 5000 seconds, or well over an hour.
Yeah, that looks like what happens if no data is coming in. The code has been introduced by 0c98279, with some good reasoning (although still an excessive 18 sec wait in theory), and later got changed to what we have since 6f3dd4d. However, even drastically reducing this wait won't help in this case, since the timeout is on select() which won't timeout for pipes on Windows.
However, even drastically reducing this wait won't help in this case, since the timeout is on select() which won't timeout for pipes on Windows.
So I modified php_select() to not select read pipes without data:
win32/select.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/win32/select.c b/win32/select.c
index f443325cf0..f404ae3982 100644
--- a/win32/select.c+++ b/win32/select.c@@ -135,7 +135,12 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e
for (i = 0; i < n_handles; i++) {
if (WAIT_OBJECT_0 == WaitForSingleObject(handles[i], 0)) {
if (SAFE_FD_ISSET(handle_slot_to_fd[i], rfds)) {
- FD_SET((uint32_t)handle_slot_to_fd[i], &aread);+ DWORD avail_read = 0;+ if (!PeekNamedPipe(handles[i], NULL, 0, NULL, &avail_read, NULL) || avail_read > 0) {+ FD_SET((uint32_t)handle_slot_to_fd[i], &aread);+ } else {+ retcode--;+ }
}
if (SAFE_FD_ISSET(handle_slot_to_fd[i], wfds)) {
FD_SET((uint32_t)handle_slot_to_fd[i], &awrite);
The timeout works fine this way, but terminating the process won't work (so there are errors reported in the following), because we're going through the shell (cmd.exe) and killing this process, won't kill php.exe. I don't think there is any way to kill grandchild processes from PHP, and even doing this in C would be tedious (would need to enumerate all processes, and kill those with the given parent procid; not even sure what to do with grandchildren). The alternative would be to bypass the shell, but that will not work if there are redirects involved.
However, even drastically reducing this wait won't help in this case, since the timeout is on select() which won't timeout for pipes on Windows.
So I modified php_select() to not select read pipes without data:
win32/select.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/win32/select.c b/win32/select.c
index f443325cf0..f404ae3982 100644
--- a/win32/select.c+++ b/win32/select.c@@ -135,7 +135,12 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e
for (i = 0; i < n_handles; i++) {
if (WAIT_OBJECT_0 == WaitForSingleObject(handles[i], 0)) {
if (SAFE_FD_ISSET(handle_slot_to_fd[i], rfds)) {
- FD_SET((uint32_t)handle_slot_to_fd[i], &aread);+ DWORD avail_read = 0;+ if (!PeekNamedPipe(handles[i], NULL, 0, NULL, &avail_read, NULL) || avail_read > 0) {+ FD_SET((uint32_t)handle_slot_to_fd[i], &aread);+ } else {+ retcode--;+ }
}
if (SAFE_FD_ISSET(handle_slot_to_fd[i], wfds)) {
FD_SET((uint32_t)handle_slot_to_fd[i], &awrite);
The timeout works fine this way, but terminating the process won't work (so there are errors reported in the following), because we're going through the shell (cmd.exe) and killing this process, won't kill php.exe. I don't think there is any way to kill grandchild processes from PHP, and even doing this in C would be tedious (would need to enumerate all processes, and kill those with the given parent procid; not even sure what to do with grandchildren). The alternative would be to bypass the shell, but that will not work if there are redirects involved.
Do you know why select wasn't implemented this way to begin with? Seems like a simple enough fix considering the impact of this issue & how often it's been reported.
No, I have no idea. Maybe that has just been overlooked, or not deemed a holistic approach (after all, only read pipes would be catered to; and the proper solution for having non blocking pipes on Windows would be overlapped IO). Or maybe few use select() with pipes on Windows (maybe because it is useless). See e.g. https://bugs.php.net/51800 (which triggered the PeekNamedPipe() hack in php_stdiop_read()); no select(), just trying to read. Or maybe it is because select(2) is considered obsoleted by poll(2) anyway.
Anyhow, I'm going to re-purpose this ticket for the more general stream_select() issue. We can tackle other issues with run-tests.php via different tickets.
Activity
cmb69 commentedon Nov 21, 2024
Sad story:
php-src/win32/select.c
Lines 20 to 30 in f44eaac
and then:
php-src/main/streams/plain_wrapper.c
Lines 398 to 421 in f44eaac
So if there is nothing to read, we sleep for 32 seconds (in theory; likely way more in practice).
Something enlightening for German speakers: https://learn.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
dktapps commentedon Nov 21, 2024
I may be wrong on the specifics but I do know those tests got stuck for way longer than 32 seconds. I ended up having to abort them after about half an hour or so. Very suboptimal either way lol
dktapps commentedon Nov 21, 2024
With the default scheduler frequency on Windows (assuming usleep doesn't spin-wait; I didn't look at the impl) this could sleep for as long as 5000 seconds, or well over an hour.
cmb69 commentedon Nov 21, 2024
Yeah, that looks like what happens if no data is coming in. The code has been introduced by 0c98279, with some good reasoning (although still an excessive 18 sec wait in theory), and later got changed to what we have since 6f3dd4d. However, even drastically reducing this wait won't help in this case, since the timeout is on
select()which won't timeout for pipes on Windows.cmb69 commentedon Nov 22, 2024
So I modified
php_select()to not select read pipes without data:The timeout works fine this way, but terminating the process won't work (so there are errors reported in the following), because we're going through the shell (cmd.exe) and killing this process, won't kill php.exe. I don't think there is any way to kill grandchild processes from PHP, and even doing this in C would be tedious (would need to enumerate all processes, and kill those with the given parent procid; not even sure what to do with grandchildren). The alternative would be to bypass the shell, but that will not work if there are redirects involved.
dktapps commentedon Nov 22, 2024
Do you know why select wasn't implemented this way to begin with? Seems like a simple enough fix considering the impact of this issue & how often it's been reported.
cmb69 commentedon Nov 24, 2024
No, I have no idea. Maybe that has just been overlooked, or not deemed a holistic approach (after all, only read pipes would be catered to; and the proper solution for having non blocking pipes on Windows would be overlapped IO). Or maybe few use
select()with pipes on Windows (maybe because it is useless). See e.g. https://bugs.php.net/51800 (which triggered thePeekNamedPipe()hack inphp_stdiop_read()); noselect(), just trying to read. Or maybe it is becauseselect(2)is considered obsoleted bypoll(2)anyway.Anyhow, I'm going to re-purpose this ticket for the more general
stream_select()issue. We can tackle other issues with run-tests.php via different tickets.[-]`run-tests.php` timeout doesn't work on Windows[/-][+]stream_select() timeout useless for pipes on Windows[/+]phpGH-16889: stream_select() timeout useless for pipes on Windows
GH-16889: stream_select() timeout useless for pipes on Windows
cmb69 commentedon Dec 7, 2024
Fixed via b614b4a (master branch only).
Improve fix for phpGH-16889
Improve fix for GH-16889