diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 4e7b01d..79cede9 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -183,23 +183,8 @@ public function read(?Cancellation $cancellation = null, ?int $limit = null): ?s \assert($this->resource !== null); - \set_error_handler(self::$errorHandler); - - try { - // Attempt a direct read because PHP may buffer data, e.g. in TLS buffers. - if ($this->useSingleRead) { - $data = \fread($this->resource, $limit); - } else { - $data = \stream_get_contents($this->resource, $limit); - } - } finally { - \restore_error_handler(); - } - - \assert( - $data !== false, - "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to." - ); + // Attempt a direct read because PHP may buffer data, e.g. in TLS buffers. + $data = $this->readDirectly($limit); if ($data === '') { if (\feof($this->resource)) { @@ -208,6 +193,13 @@ public function read(?Cancellation $cancellation = null, ?int $limit = null): ?s return null; } + // On a TLS stream feof() calls SSL_peek(), which moves a record that has just arrived from the socket + // into the OpenSSL buffer. The socket is then no longer readable, so the watcher would never be invoked + // by drivers polling the descriptor (all except StreamSelectDriver, as stream_select() checks SSL_pending()). + $data = $this->readDirectly($limit); + } + + if ($data === '') { $this->chunkSize = $limit; EventLoop::enable($this->callbackId); $this->suspension = EventLoop::getSuspension(); @@ -334,6 +326,30 @@ public function __destruct() } } + private function readDirectly(int $limit): string + { + \assert($this->resource !== null); + + \set_error_handler(self::$errorHandler); + + try { + if ($this->useSingleRead) { + $data = \fread($this->resource, $limit); + } else { + $data = \stream_get_contents($this->resource, $limit); + } + } finally { + \restore_error_handler(); + } + + \assert( + $data !== false, + "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to." + ); + + return $data; + } + /** * Nulls reference to resource, marks stream unreadable, and succeeds any pending read with null. */