diff --git a/.travis.yml b/.travis.yml index d74b621..4f8956f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4 - nightly matrix: diff --git a/lib/ResourceOutputStream.php b/lib/ResourceOutputStream.php index dea2b1a..b8e2d42 100644 --- a/lib/ResourceOutputStream.php +++ b/lib/ResourceOutputStream.php @@ -83,10 +83,22 @@ public function __construct($stream, int $chunkSize = null) $written = @\fwrite($stream, $data); } - \assert($written !== false, "Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to."); + \assert( + $written !== false || \PHP_VERSION_ID >= 70400, // PHP 7.4+ returns false on EPIPE. + "Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the still referenced in the loop." + ); + + // PHP 7.4.0 and 7.4.1 may return false on EAGAIN. + if ($written === false && \PHP_VERSION_ID >= 70402) { + $message = "Failed to write to stream"; + if ($error = \error_get_last()) { + $message .= \sprintf("; %s", $error["message"]); + } + throw new StreamException($message); + } // Broken pipes between processes on macOS/FreeBSD do not detect EOF properly. - if ($written === 0) { + if ($written === 0 || $written === false) { if ($emptyWrites++ > self::MAX_CONSECUTIVE_EMPTY_WRITES) { $message = "Failed to write to stream after multiple attempts"; if ($error = \error_get_last()) { @@ -191,7 +203,21 @@ private function send(string $data, bool $end = false): Promise $written = @\fwrite($this->resource, $data); } - \assert($written !== false, "Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to."); + \assert( + $written !== false || \PHP_VERSION_ID >= 70400, // PHP 7.4+ returns false on EPIPE. + "Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the still referenced in the loop." + ); + + // PHP 7.4.0 and 7.4.1 may return false on EAGAIN. + if ($written === false && \PHP_VERSION_ID >= 70402) { + $message = "Failed to write to stream"; + if ($error = \error_get_last()) { + $message .= \sprintf("; %s", $error["message"]); + } + return new Failure(new StreamException($message)); + } + + $written = (int) $written; // Cast potential false to 0. if ($length === $written) { if ($end) { diff --git a/test/ResourceOutputStreamTest.php b/test/ResourceOutputStreamTest.php index e186e12..ab1185c 100644 --- a/test/ResourceOutputStreamTest.php +++ b/test/ResourceOutputStreamTest.php @@ -44,7 +44,7 @@ public function testBrokenPipe() \fclose($b); $this->expectException(StreamException::class); - $this->expectExceptionMessage("Failed to write to stream after multiple attempts; fwrite(): send of 6 bytes failed with errno=32 Broken pipe"); + $this->expectExceptionMessage("fwrite(): send of 6 bytes failed with errno=32 Broken pipe"); wait($stream->write("foobar")); } @@ -60,7 +60,7 @@ public function testClosedRemoteSocket() \fclose($b); $this->expectException(StreamException::class); - $this->expectExceptionMessage("Failed to write to stream after multiple attempts; fwrite(): send of 6 bytes failed with errno=32 Broken pipe"); + $this->expectExceptionMessage("fwrite(): send of 6 bytes failed with errno=32 Broken pipe"); // The first write still succeeds somehow... wait($stream->write("foobar"));