Page MenuHomePhabricator

D7748.id17515.diff
No OneTemporary

D7748.id17515.diff

Index: src/channel/PhutilSocketChannel.php
===================================================================
--- src/channel/PhutilSocketChannel.php
+++ src/channel/PhutilSocketChannel.php
@@ -132,7 +132,7 @@
return 0;
}
- $len = @fwrite($socket, $bytes);
+ $len = phutil_fwrite_nonblocking_stream($socket, $bytes);
if ($len === false) {
$this->closeWriteSocket();
return 0;
Index: src/utils/utils.php
===================================================================
--- src/utils/utils.php
+++ src/utils/utils.php
@@ -922,3 +922,69 @@
return $result;
}
+
+
+/**
+ * Perform an `fwrite()` which distinguishes between EAGAIN and EPIPE.
+ *
+ * PHP's `fwrite()` is broken, and never returns `false` for writes to broken
+ * nonblocking pipes: it always returns 0, and provides no straightforward
+ * mechanism for distinguishing between EAGAIN (buffer is full, can't write any
+ * more right now) and EPIPE or similar (no write will ever succeed).
+ *
+ * See: https://bugs.php.net/bug.php?id=39598
+ *
+ * After attempting an `fwrite()` and having it return `0`, you can call this
+ * method on the pipe socket to detect (hopefully) if the zero-length write
+ * was caused by EAGAIN or something else.
+ *
+ * @param resource Socket or pipe stream.
+ * @param string Bytes to write
+ * @return bool|int Bytes written, or false on error, even if that error is
+ * EPIPE.
+ */
+function phutil_fwrite_nonblocking_stream($stream, $bytes) {
+ if (!strlen($bytes)) {
+ return 0;
+ }
+
+ $result = @fwrite($stream, $bytes);
+ if ($result !== 0) {
+ // In cases where some bytes are witten (`$result > 0`) or
+ // an error occurs (`$result === false`), the behavior of fwrite() is
+ // correct. We can return the value as-is.
+ return $result;
+ }
+
+ // If we make it here, we performed a 0-length write. Try to distinguish
+ // between EAGAIN and EPIPE. To do this, we're going to `stream_select()`
+ // the stream, write to it again if PHP claims that it's writable, and
+ // consider the pipe broken if the write fails.
+
+ $read = array();
+ $write = array($stream);
+ $except = array();
+
+ @stream_select($read, $write, $except, 0);
+
+ if (!$write) {
+ // The stream isn't writable, so we conclude that it probably really is
+ // blocked and the underlying error was EAGAIN. Return 0 to indicate that
+ // no data could be written yet.
+ return 0;
+ }
+
+ // If we make it here, PHP **just** claimed that this stream is writable, so
+ // perform a write. If the write also fails, conclude that these failures are
+ // EPIPE or some other permanent failure.
+ $result = @fwrite($stream, $bytes);
+ if ($result !== 0) {
+ // The write worked or failed explicitly. This value is fine to return.
+ return $result;
+ }
+
+ // We performed a 0-length write, were told that the stream was writable, and
+ // then immediately performed another 0-length write. Conclude that the pipe
+ // is broken and return `false.
+ return false;
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Mar 23, 6:12 PM (5 d, 6 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7718821
Default Alt Text
D7748.id17515.diff (2 KB)

Event Timeline