Skip to content

Commit

Permalink
Compatibility with 7.4 EAGAIN and EPIPE changes (#71)
Browse files Browse the repository at this point in the history
* Workaround for 7.4.0 and 7.4.1 EAGAIN bug

See PHP bug 79000.

* Test on 7.4

* EPIPE now returns false

* Less specific exception message

On macOS fwrite is returning false yet for EPIPE, but apparently not on travis.
  • Loading branch information
trowski committed Jan 29, 2020
1 parent 9d82056 commit 1e52f17
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 5 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -7,6 +7,7 @@ php:
- 7.1
- 7.2
- 7.3
- 7.4
- nightly

matrix:
Expand Down
32 changes: 29 additions & 3 deletions lib/ResourceOutputStream.php
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions test/ResourceOutputStreamTest.php
Expand Up @@ -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"));
}

Expand All @@ -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"));
Expand Down

0 comments on commit 1e52f17

Please sign in to comment.