Repository navigation
Conversation
php_network_connect_socket() switches the socket to non-blocking mode for the connect and restores the original mode afterwards for synchronous connects. On Windows, SET_SOCKET_BLOCKING_MODE() sets `save = TRUE` and FIONBIO does not write back the previous mode, so RESTORE_SOCKET_BLOCKING_MODE() set the socket to non-blocking again. Winsock cannot query a socket's blocking mode, but all callers pass freshly created (blocking) sockets, so restore to blocking. This matches the POSIX behavior. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
I m pretty sure the fix is incorrect, part of it is MSG_DONTWAIT is 0 on windows. @shivammathur wdyt ? |
Yes, you are right. Do you want me to withdraw this PR, or let Claude give another try? I'm not really sure what happened inside. |
|
I believe the bug is real so it s worth fixing. AI or not however having a real windows expert looking into it is valuable. |
|
@vibbow |
Windows has no MSG_DONTWAIT, so now that the socket is restored to blocking mode after a synchronous connect, send() could block past the stream timeout. Switch to non-blocking mode for the duration of a timed write and restore blocking mode afterwards, as xp_ssl.c does. This also fixes timed writes on accepted sockets and after stream_set_blocking(true), which were already blocking on Windows. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@devnexen @shivammathur Thanks. I've updated it as suggested: the socket is still restored to blocking after a synchronous connect, and On Windows, |
A write that times out may still have written part of the chunk, so fwrite() returns a non-zero length. On macOS this happened on many iterations, and the test hit the run-tests timeout. Stop at the first write that reports a timeout instead. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test still hit the run-tests timeout on macOS, although the fix is Windows only, so timed writes to a stalled loopback peer don't time out reliably there either way. The bug and the fix are Windows specific, so limit the test to Windows like gh24173.phpt. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
|
|
I will check it out including the other PR's in the next few weeks. I have spent actually quite a bit of time with Windows IO recently. |
bukka
left a comment
There was a problem hiding this comment.
I did an initial review but there potentially some other issue. This will need more careful review and considering thing.
Please also do not use Claude as a co-author. In this case, it might be just better if I fix it myself because it requires some understanding of the things around and will also need more tests.
|
|
||
| #ifdef PHP_WIN32 | ||
| if (restore_blocking) { | ||
| php_set_sock_blocking(sock->socket, 1); |
There was a problem hiding this comment.
This seems to be changing semantic and can potentially break things. The issues is that with async connect php_network_connect_socket() returns early and intentionally leaves the OS socket non-blocking, but php_stream_generic_socket_factory() still sets is_blocked = 1. So the first timed fwrite() on such stream switches to non-blocking and then restores to blocking here, which is a mode the socket never had. A later stream_socket_recvfrom() or stream_socket_sendto() then blocks indefinitely on Windows while it still returns immediately on POSIX.
The actual problem is that is_blocked tracks what PHP wants and not what the OS socket is, and Winsock cannot query the real mode. We should probably track it separately (there seems to be still space to do it in ABI safe way in php_netstream_data_t but would need to think about it.
There was a problem hiding this comment.
Also it should check the return value here.
Fixes #24173.
php_network_connect_socket()switches the socket to non-blocking mode for the connect, and for a synchronous connect it is supposed to restore the original mode afterwards. On Windows,SET_SOCKET_BLOCKING_MODE()setssave = TRUE, andFIONBIOdoesn't write back the previous mode. SoRESTORE_SOCKET_BLOCKING_MODE()passedTRUEagain, and the socket stayed non-blocking while the stream reportedblocked => true. Paths that callrecv()without polling first, such asstream_socket_recvfrom()andsocket_import_stream()+socket_read(), then failed immediately withWSAEWOULDBLOCK.Winsock can't query a socket's blocking mode, but all callers of
php_network_connect_socket()pass freshly created sockets, which are blocking. So this restores to blocking, which matches the POSIX behavior. Async connects don't callRESTORE, so they are unaffected.Since Windows has no
MSG_DONTWAIT, timed writes relied on the socket being left non-blocking: with a blocking socket,send()inphp_sockop_write()could block past the stream timeout. Sophp_sockop_write()now switches to non-blocking mode for the duration of a timed write on Windows and restores blocking mode afterwards, likexp_ssl.cdoes. This also fixes timed writes on accepted sockets and afterstream_set_blocking($s, true), which were already blocking and hung on Windows before this change.Tests:
gh24173.phpt(Windows only): a socket imported from a client stream is blocking.gh24173_write_timeout.phpt(Windows only): a timedfwrite()to a peer that doesn't read times out, for both a client socket and an accepted socket.Tested on Windows 11 with an 8.4 NTS x64 build (built with php-windows-builder):
fwrite()withstream_set_timeout()ordefault_socket_timeouttimes out on client sockets, accepted sockets, and afterstream_set_blocking(true).stream_socket_recvfrom()andsocket_import_stream()+socket_read()wait for data.fread()/fgets()timeouts,feof()and non-blocking writes are unchanged.ext/standard/tests/streams,ext/standard/tests/networkandext/sockets/testshave the same results as before the change.Also tested on Ubuntu 26.04: the same test directories pass, and behavior is unchanged (the code changes are Windows only).
This is independent of #24172 (GH-24171), which touches the same function.
🤖 Generated with Claude Code