Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15423779
D7748.id17515.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
2 KB
Referenced Files
None
Subscribers
None
D7748.id17515.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7748: Work around broken PHP fwrite() on nonblocking pipes
Attached
Detach File
Event Timeline
Log In to Comment