diff --git a/NEWS b/NEWS index 2595e055ddfe..0d38958818ef 100644 --- a/NEWS +++ b/NEWS @@ -85,6 +85,9 @@ PHP NEWS - Streams: . Applied the so_linger, so_rcvbuf and so_sndbuf context options to unix sockets. (David Carlier) + . stream_socket_accept() no longer reports an error on a zero timeout with no + pending connection and reports TimeOut instead of AcceptFailed on other + timeouts (GH-23879). (Lazizbek Ergashev) - Standard: . Fixed bug #60110 (fclose(), file_put_contents(), copy() do not return false diff --git a/UPGRADING b/UPGRADING index a6cdf9829ab1..7cdc3b5f9fbc 100644 --- a/UPGRADING +++ b/UPGRADING @@ -28,6 +28,9 @@ PHP 8.7 UPGRADE NOTES . The number of filters that can be chained in a php://filter URL is limited to 16 by default. Set the stream context option max_filter_count to change this. + . stream_socket_accept() no longer reports an error when the timeout is 0 + and no connection is pending. It still returns false. When a non-zero + timeout expires, the error code is now TimeOut instead of AcceptFailed. ======================================== 2. New Features diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index efcf9c00544e..1c1bce27eb1b 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -45,6 +45,9 @@ PHP 8.7 INTERNALS UPGRADE NOTES - The INIT_CLASS_ENTRY_INIT_METHODS() macro, which was previously used as part of handling disabled classes, was removed following the removal of the `disable_classes` INI setting in PHP 8.5. +- php_stream_xport_accept() now returns 0 with a NULL client when no + connection is pending before the timeout. Callers must check the client, + not only the return value. ======================== 2. Build system changes diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 59be6503e00c..5217e732a938 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -3557,6 +3557,10 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_ } } } + } else if (xparam->outputs.error_code == PHP_TIMEOUT_ERROR_VALUE + || PHP_IS_TRANSIENT_ERROR(xparam->outputs.error_code)) { + /* No pending connection, the caller decides how to report it. */ + return 0; } return xparam->outputs.client == NULL ? -1 : 0; diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 6502ac279e26..bc27e694589a 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,11 @@ 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"); + } else if (!tv_pointer || tv.tv_sec || tv.tv_usec) { + php_stream_warn(stream, TimeOut, "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..913df19e283a --- /dev/null +++ b/ext/standard/tests/streams/gh23879.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-23879 (stream_socket_accept() reports AcceptFailed when no connection is pending) +--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); +var_dump(stream_socket_accept($server, 0)); + +try { + stream_socket_accept($server, 0.1); +} catch (StreamException $e) { + echo $e->getMessage(), "\n"; + echo $e->getErrors()[0]->code->name, "\n"; +} +?> +--EXPECTF-- +bool(false) +array(0) { +} +bool(true) +bool(false) +bool(false) +Accept failed: %s +TimeOut diff --git a/main/network.c b/main/network.c index 1eee7390a23a..850e079ddae1 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 5c2cb0acede2..6782a965d6a2 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 6c408dab1127..a20d4f833565 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -1078,6 +1078,10 @@ static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t GC_ADDREF(stream->ctx); } } + } else if (xparam->outputs.error_code == PHP_TIMEOUT_ERROR_VALUE + || PHP_IS_TRANSIENT_ERROR(xparam->outputs.error_code)) { + /* No pending connection, the caller decides how to report it. */ + return 0; } return xparam->outputs.client == NULL ? -1 : 0;