[php-src] PR #24018: Fix StreamPollHandle changing unread_bytes of a TLS stream when added
| From: | bukka | Date: | Wed, 30 Sep 2026 14:45:35 +0000 |
| Subject: | [php-src] PR #24018: Fix StreamPollHandle changing unread_bytes of a TLS stream when added | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-38992@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24018
Author: bukka
StreamPollHandle took the descriptor through the select cast. On a TLS stream that cast reads: it
moves what OpenSSL holds decrypted into the stream buffer so that stream_select() can report it.
Adding a watcher to an Io\Poll\Context therefore read from the stream, filled the buffer of a stream
set unbuffered with stream_set_read_buffer() and changed unread_bytes. That is not how the poll API
should behave. Adding a handle is a registration and must leave the stream as it found it, and the
data that moved was not reported by the watcher anyway, as bytes held by the stream layer are not
readiness of the descriptor.
A new cast PHP_STREAM_AS_FD_FOR_POLL returns the descriptor and nothing else. The socket, TLS, plain
and pgsql wrappers answer it, a userspace wrapper sees it as STREAM_CAST_FOR_SELECT and the stream
it returns is cast the same way, and a filtered stream allows it like the select cast.
StreamPollHandle uses it, so adding a TLS stream leaves its buffer and unread_bytes as they were.
The select cast and stream_select() are unchanged.
This is fixed in 8.6 because the API is new there and this is the behaviour it should ship with.
Keeping a read inside the registration would complicate things later: once reads on a TLS stream can
suspend, the cast would become a nested read on a stream with an operation in flight, and changing
the cast then would change the visible behaviour of a released API.