diff --git a/NEWS b/NEWS index 785273bed94e..34031f2d77c6 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.6.0RC3 +- Streams: + . Fixed bug GH-23879 (stream_socket_accept() reports AcceptFailed when a + non-blocking listener has no pending connection). (Lazizbek Ergashev) 24 Sep 2026, PHP 8.6.0RC2 diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 8bbdc5caabe5..969408c7aa46 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -184,6 +184,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES zval* that is IS_UNDEF when the closure is unbound. . object_properties_load() now verifies that the given value is assignable to typed properties. The check is performed in strict mode. + . php_stream_xport_accept() now returns 0 with a NULL client when a + non-blocking listener has no pending connection. Callers must check the + client, not only the return value. - Added: . New zend_class_entry.ce_flags2 and zend_function.fn_flags2 fields were diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 24895a960043..e2c7da8deebc 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3556,6 +3556,10 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_ } } } + } else if (!sock->s.is_blocked && (xparam->outputs.error_code == PHP_TIMEOUT_ERROR_VALUE + || PHP_IS_TRANSIENT_ERROR(xparam->outputs.error_code))) { + /* No pending connection is not an error for a non-blocking listener. */ + return 0; } return xparam->outputs.client == NULL ? -1 : 0; diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index a03aed1ccdd4..4a2fe1065851 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -314,11 +314,13 @@ PHP_FUNCTION(stream_socket_accept) php_stream_error_operation_begin(); - if (0 == php_stream_xport_accept(stream, &clistream, + int ret = php_stream_xport_accept(stream, &clistream, zpeername ? &peername : NULL, NULL, NULL, tv_pointer, &errstr - ) && clistream) { + ); + + if (0 == ret && clistream) { if (peername) { ZEND_TRY_ASSIGN_REF_STR(zpeername, peername); @@ -328,7 +330,9 @@ PHP_FUNCTION(stream_socket_accept) if (peername) { zend_string_release(peername); } - php_stream_warn(stream, AcceptFailed, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error"); + if (0 != ret) { + php_stream_warn(stream, AcceptFailed, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error"); + } RETVAL_FALSE; } diff --git a/ext/standard/tests/streams/gh23879.phpt b/ext/standard/tests/streams/gh23879.phpt new file mode 100644 index 000000000000..2b605efbb881 --- /dev/null +++ b/ext/standard/tests/streams/gh23879.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-23879 (stream_socket_accept() reports AcceptFailed when a non-blocking listener has no pending connection) +--FILE-- + [ + 'error_mode' => StreamErrorMode::Exception, + 'error_store' => StreamErrorStore::All, + ], + ]), +); +stream_set_blocking($server, false); + +var_dump(stream_socket_accept($server, 0)); +var_dump(stream_last_errors()); + +$client = stream_socket_client('tcp://' . stream_socket_get_name($server, false)); +var_dump(is_resource(stream_socket_accept($server, 1))); +var_dump(stream_socket_accept($server, 0)); + +stream_set_blocking($server, true); +try { + stream_socket_accept($server, 0); +} catch (StreamException $e) { + var_dump($e->getErrors()[0]->code); +} +?> +--EXPECT-- +bool(false) +array(0) { +} +bool(true) +bool(false) +enum(StreamErrorCode::AcceptFailed) diff --git a/main/network.c b/main/network.c index 01e4f2e778d4..e933bce6e849 100644 --- a/main/network.c +++ b/main/network.c @@ -69,7 +69,6 @@ # include "win32/time.h" # define SOCK_ERR INVALID_SOCKET # define SOCK_CONN_ERR SOCKET_ERROR -# define PHP_TIMEOUT_ERROR_VALUE WSAETIMEDOUT #ifdef HAVE_IPV6 const struct in6_addr in6addr_any = {0}; /* IN6ADDR_ANY_INIT; */ @@ -78,7 +77,6 @@ const struct in6_addr in6addr_any = {0}; /* IN6ADDR_ANY_INIT; */ #else # define SOCK_ERR -1 # define SOCK_CONN_ERR -1 -# define PHP_TIMEOUT_ERROR_VALUE ETIMEDOUT #endif #ifdef HAVE_GETADDRINFO diff --git a/main/php_network.h b/main/php_network.h index c93a519911f0..49c5099f50d7 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -109,10 +109,12 @@ typedef int php_socket_t; # define SOCK_ERR INVALID_SOCKET # define SOCK_CONN_ERR SOCKET_ERROR # define SOCK_RECV_ERR SOCKET_ERROR +# define PHP_TIMEOUT_ERROR_VALUE WSAETIMEDOUT #else # define SOCK_ERR -1 # define SOCK_CONN_ERR -1 # define SOCK_RECV_ERR -1 +# define PHP_TIMEOUT_ERROR_VALUE ETIMEDOUT #endif #define STREAM_SOCKOP_NONE (1 << 0) diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 3844414d8048..f2b0510d079e 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -1086,6 +1086,10 @@ static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t GC_ADDREF(stream->ctx); } } + } else if (!sock->is_blocked && (xparam->outputs.error_code == PHP_TIMEOUT_ERROR_VALUE + || PHP_IS_TRANSIENT_ERROR(xparam->outputs.error_code))) { + /* No pending connection is not an error for a non-blocking listener. */ + return 0; } return xparam->outputs.client == NULL ? -1 : 0;