Description
stream_set_timeout() passes PHP_STREAM_OPTION_READ_TIMEOUT to the stream wrapper:
|
#endif |
|
|
|
php_stream_error_operation_begin(); |
|
RETVAL_BOOL(PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t)); |
|
php_stream_error_operation_end_for_stream(stream); |
|
} |
The constant and its comment explicitly describe a read timeout:
|
/* set the timeout duration for reads on the stream. ptrparam is a pointer to a struct timeval * */ |
|
#define PHP_STREAM_OPTION_READ_TIMEOUT 4 |
|
#define PHP_STREAM_OPTION_SET_CHUNK_SIZE 5 |
However, the native socket stream stores this option in the single sock->timeout field:
|
case PHP_STREAM_OPTION_READ_TIMEOUT: |
|
sock->timeout = *(struct timeval*)ptrparam; |
|
sock->timeout_event = false; |
|
return PHP_STREAM_OPTION_RETURN_OK; |
That timeout is used by both php_sockop_read() and php_sockop_write(). In particular, a blocking write which encounters EAGAIN/EWOULDBLOCK waits for POLLOUT using the same timeout:
|
static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t count) |
|
{ |
|
php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; |
|
ssize_t didwrite; |
|
struct timeval *ptimeout; |
|
|
|
if (!sock || sock->socket == -1) { |
|
return 0; |
|
} |
|
|
|
if (sock->timeout.tv_sec == -1) |
|
ptimeout = NULL; |
|
else |
|
ptimeout = &sock->timeout; |
|
|
|
retry: |
|
didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0); |
|
|
|
if (didwrite <= 0) { |
|
char *estr; |
|
int err = php_socket_errno(); |
|
|
|
if (PHP_IS_TRANSIENT_ERROR(err)) { |
|
if (sock->is_blocked) { |
|
int retval; |
|
|
|
sock->timeout_event = false; |
|
|
|
do { |
|
retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout); |
|
|
|
if (retval == 0) { |
|
sock->timeout_event = true; |
|
break; |
|
} |
|
|
|
if (retval > 0) { |
|
/* writable now; retry */ |
|
goto retry; |
|
} |
|
|
|
err = php_socket_errno(); |
|
} while (err == EINTR); |
|
} else { |
|
/* EWOULDBLOCK/EAGAIN is not an error for a non-blocking stream. |
|
* Report zero byte write instead. */ |
|
return 0; |
|
} |
|
} |
|
|
|
if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { |
|
estr = php_socket_strerror(err, NULL, 0); |
|
php_stream_warn(stream, NetworkSendFailed, |
|
"Send of %zu bytes failed with errno=%d %s", count, err, estr); |
|
efree(estr); |
|
} |
|
} |
|
|
|
if (didwrite > 0) { |
|
php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); |
|
} |
|
|
|
return didwrite; |
|
} |
This creates an ambiguity for extension and alternative stream-wrapper implementers: following the constant name and header comment produces a read-only implementation, while matching native PHP socket stream behavior requires applying the value to both blocking reads and blocking writes.
This ambiguity caused a real compatibility issue in Swoole's coroutine stream hook, where stream_set_timeout() changed the read timeout but a blocked fwrite() continued using the previous write timeout:
swoole/swoole-src#6260
The PHP manual currently says only that the function “sets the timeout value on the stream”; it does not document that native network/socket streams use the value for writes as well, and its example demonstrates only a read timeout:
https://github.com/php/doc-en/blob/master/reference/stream/functions/stream-set-timeout.xml
Suggested changes:
- Clarify the
stream_set_timeout() documentation: for native network/socket streams, the configured timeout applies to blocking reads and blocking writes.
- Update the misleading comment in
main/php_streams.h.
- Introduce a direction-neutral internal name such as
PHP_STREAM_OPTION_IO_TIMEOUT or PHP_STREAM_OPTION_TIMEOUT, while retaining PHP_STREAM_OPTION_READ_TIMEOUT as an alias for source compatibility, and migrate in-tree users to the clearer name.
- Consider adding a php-src regression test covering a blocked socket-stream write after
stream_set_timeout().
No runtime behavior change is being requested here; the goal is to make the existing behavior unambiguous and prevent downstream implementations from interpreting the option differently.
Description
stream_set_timeout()passesPHP_STREAM_OPTION_READ_TIMEOUTto the stream wrapper:php-src/ext/standard/streamsfuncs.c
Lines 1494 to 1499 in 810ac6d
The constant and its comment explicitly describe a read timeout:
php-src/main/php_streams.h
Lines 448 to 450 in 810ac6d
However, the native socket stream stores this option in the single
sock->timeoutfield:php-src/main/streams/xp_socket.c
Lines 407 to 410 in 810ac6d
That timeout is used by both
php_sockop_read()andphp_sockop_write(). In particular, a blocking write which encountersEAGAIN/EWOULDBLOCKwaits forPOLLOUTusing the same timeout:php-src/main/streams/xp_socket.c
Lines 65 to 128 in 810ac6d
This creates an ambiguity for extension and alternative stream-wrapper implementers: following the constant name and header comment produces a read-only implementation, while matching native PHP socket stream behavior requires applying the value to both blocking reads and blocking writes.
This ambiguity caused a real compatibility issue in Swoole's coroutine stream hook, where
stream_set_timeout()changed the read timeout but a blockedfwrite()continued using the previous write timeout:swoole/swoole-src#6260
The PHP manual currently says only that the function “sets the timeout value on the stream”; it does not document that native network/socket streams use the value for writes as well, and its example demonstrates only a read timeout:
https://github.com/php/doc-en/blob/master/reference/stream/functions/stream-set-timeout.xml
Suggested changes:
stream_set_timeout()documentation: for native network/socket streams, the configured timeout applies to blocking reads and blocking writes.main/php_streams.h.PHP_STREAM_OPTION_IO_TIMEOUTorPHP_STREAM_OPTION_TIMEOUT, while retainingPHP_STREAM_OPTION_READ_TIMEOUTas an alias for source compatibility, and migrate in-tree users to the clearer name.stream_set_timeout().No runtime behavior change is being requested here; the goal is to make the existing behavior unambiguous and prevent downstream implementations from interpreting the option differently.