Sockets from synchronous stream_socket_client() are left in non-blocking mode on Windows
Mantenedores costumam responder em até 1 dia
Avaliação
- Dificuldade
- 2/5
- Tempo estimado
- 1-3 horas
- Facilidade para iniciantes
- 66/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Domínio
- backend, networking
Direção de pesquisa
Comece em main/network.c e inspecione as macros do Windows SET_SOCKET_BLOCKING_MODE e RESTORE_SOCKET_BLOCKING_MODE usadas por php_network_connect_socket(). A issue inclui uma alteração proposta e um script de reprodução; verifique se as conexões síncronas restauram o modo bloqueante, enquanto as assíncronas permanecem inalteradas, e depois execute os testes relevantes de rede e stream no Windows. O objetivo é que as leituras relatadas aguardem os dados sem prejudicar o comportamento de timeout ou não bloqueante.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Description
On Windows, a socket created by a synchronous stream_socket_client() / fsockopen() connect stays in non-blocking mode at the OS level, while the stream reports blocked => true. fread()/fwrite() hide this because they poll before calling recv()/send(). Paths that call recv() directly don't, so they fail immediately instead of waiting for data:
stream_socket_recvfrom()returnsfalseat once.socket_import_stream()marks the imported socket as blocking (it copies the stream'sis_blocked), butsocket_read()/socket_recv()fail immediately withWSAEWOULDBLOCK(10035).
I found this while investigating #24171. It is a separate bug and independent of that fix.
The following code:
<?php
// Child process: a server that replies "hello" to each connection 300 ms
// after accepting it.
$server = <<<'SRV'
$srv = stream_socket_server('tcp://127.0.0.1:0', $en, $es);
echo stream_socket_get_name($srv, false), "\n";
$pending = [];
while (true) {
$r = [$srv]; $w = $e = null;
if (stream_select($r, $w, $e, 0, 10000)) {
$pending[] = [stream_socket_accept($srv), hrtime(true) + 300e6];
}
foreach ($pending as $i => [$c, $due]) {
if (hrtime(true) >= $due) {
@fwrite($c, "hello");
fclose($c);
unset($pending[$i]);
}
}
}
SRV;
$proc = proc_open([PHP_BINARY, '-n', '-r', $server], [1 => ['pipe', 'w']], $pipes);
$addr = trim(fgets($pipes[1]));
function test(string $name, string $addr, callable $fn): void {
$c = stream_socket_client("tcp://$addr", $en, $es, 5);
$blocked = stream_get_meta_data($c)['blocked'] ? 'true' : 'false';
$t = hrtime(true);
$r = $fn($c);
printf("%-46s blocked=%s %-28s after %5.1f ms\n",
$name, $blocked, var_export($r, true), (hrtime(true) - $t) / 1e6);
fclose($c);
}
test('stream_socket_recvfrom()', $addr, fn($c) => stream_socket_recvfrom($c, 100));
if (extension_loaded('sockets')) {
test('socket_import_stream() + socket_read()', $addr, function ($c) {
$s = socket_import_stream($c);
$r = @socket_read($s, 100);
return $r === false ? 'false (error ' . socket_last_error($s) . ')' : $r;
});
}
test('stream_set_blocking(true) + stream_socket_recvfrom()', $addr, function ($c) {
stream_set_blocking($c, true);
return stream_socket_recvfrom($c, 100);
});
proc_terminate($proc);
Resulted in this output:
stream_socket_recvfrom() blocked=true false after 0.0 ms
socket_import_stream() + socket_read() blocked=true 'false (error 10035)' after 0.0 ms
stream_set_blocking(true) + stream_socket_recvfrom() blocked=true 'hello' after 293.7 ms
But I expected this output instead (this is the output with the fix below applied, and what the POSIX code path does):
stream_socket_recvfrom() blocked=true 'hello' after 304.2 ms
socket_import_stream() + socket_read() blocked=true 'hello' after 303.6 ms
stream_set_blocking(true) + stream_socket_recvfrom() blocked=true 'hello' after 305.2 ms
Explicitly calling stream_set_blocking($c, true) (the last line) works around it, because it calls ioctlsocket(FIONBIO, 0).
Cause
php_network_connect_socket() in main/network.c switches the socket to non-blocking mode for the connect. For a synchronous connect, it is supposed to restore the original mode afterwards. The Windows versions of the macros are:
#ifdef PHP_WIN32
typedef u_long php_non_blocking_flags_t;
# define SET_SOCKET_BLOCKING_MODE(sock, save) \
save = TRUE; ioctlsocket(sock, FIONBIO, &save)
# define RESTORE_SOCKET_BLOCKING_MODE(sock, save) \
ioctlsocket(sock, FIONBIO, &save)
SET sets save = TRUE, and FIONBIO only reads its argument; it does not write back the previous mode. So RESTORE passes TRUE again, and the socket stays non-blocking. The POSIX versions save the flags with fcntl(F_GETFL) and restore them correctly.
Proposed fix
Winsock has no way to query a socket's current blocking mode. However, every caller of php_network_connect_socket() passes a freshly created socket, which is blocking by default:
php_network_connect_socket_to_host()- the Unix socket connect in
main/streams/xp_socket.c php_connect_nonb(), used by ext/ftp for passive data connections
So restoring to blocking matches what the POSIX code does:
--- a/main/network.c
+++ b/main/network.c
@@ -286,8 +286,10 @@ PHPAPI int php_network_getaddresses(const char *host, int socktype, struct socka
typedef u_long php_non_blocking_flags_t;
# define SET_SOCKET_BLOCKING_MODE(sock, save) \
save = TRUE; ioctlsocket(sock, FIONBIO, &save)
+/* Winsock cannot query the current mode; callers pass freshly created
+ * (blocking) sockets, so restore to blocking. */
# define RESTORE_SOCKET_BLOCKING_MODE(sock, save) \
- ioctlsocket(sock, FIONBIO, &save)
+ save = FALSE; ioctlsocket(sock, FIONBIO, &save)
#else
typedef int php_non_blocking_flags_t;
# define SET_SOCKET_BLOCKING_MODE(sock, save) \
Async connects (STREAM_CLIENT_ASYNC_CONNECT) don't call RESTORE, so they are unaffected.
I tested the patch on a local 8.5.11 NTS x64 build:
- Repro script: the expected output above.
- Read timeout: after
stream_set_timeout($c, 0, 200000),fread()still times out after about 200 ms withtimed_out => true. - Non-blocking mode: after
stream_set_blocking($c, false),fread()still returns immediately. - Connects: connect latency and refused/timed-out connects are unchanged.
I'll open a PR for it.
PHP Version
PHP 8.5.11 (cli) (built: Sep 22 2026 13:51:38) (NTS Visual C++ 2022 x64)
Copyright (c) The PHP Group
Built by The PHP Group
Zend Engine v4.5.11, Copyright (c) Zend Technologies
with Xdebug v3.5.3, Copyright (c) 2002-2026, by Derick Rethans
with Zend OPcache v8.5.11, Copyright (c), by Zend Technologies
Operating System
Windows 11 Pro 10.0.26300
- Linguagem predominante
- C
- Estrelas
- 40.4k
- Forks
- 8.2k
- Merge médio
- 2d 1h
- PRs com merge (30d)
- 153
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de php/php-src
-
Feature Status: Needs Triage
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 73/100
Mantenedores costumam responder em até 1 dia
-
Variant analysis: 1 unfixed sibling safety gap in php-srcTalvez já em andamento @kamil-tekiela assumiu há 9 dias. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
php/php-src#23958 · 1 responsável ·
Mantenedores costumam responder em até 1 dia
-
sapi_lsapi_ub_write does not return bytes written in lsapi modeTalvez já em andamento Um pull request vinculado a esta issue está aberto ou já foi mesclado. AbertaBug Status: Needs Triage
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 90/100
Mantenedores costumam responder em até 1 dia
-
Bug Status: Needs Triage
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia
-
Use of PIDFile= in php-fpm.serviceTalvez já em andamento @CodedByManish assumiu há 25 dias. AbertaBug SAPI: fpm Status: Needs Triage
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 65/100
php/php-src#21740 · 1 reação ·
Mantenedores costumam responder em até 1 dia
Todas as issues de php/php-src
Issues semelhantes
-
chore(gateway): emit INFO budget reserved/settled logs for proactivity v2 (chip task_2855f4ec)Abertabackend
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
BasedHardware/omi#20940 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
kovidgoyal/kitty#10625 ·
Mantenedores costumam responder em até 1 dia
-
docs
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia
-
本機相簿無法上傳webm檔案Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 70/100
xiaojieonly/Ehviewer_CN_SXJ#2893 ·