Page MenuHomePhabricator

D7774.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -15,7 +15,6 @@
'AASTToken' => 'parser/aast/api/AASTToken.php',
'AASTTree' => 'parser/aast/api/AASTTree.php',
'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php',
- 'PhutilRope' => 'utils/PhutilRope.php',
'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php',
'AphrontDatabaseConnection' => 'aphront/storage/connection/AphrontDatabaseConnection.php',
'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php',
@@ -118,6 +117,7 @@
'PhutilCallbackFilterIterator' => 'utils/PhutilCallbackFilterIterator.php',
'PhutilChannel' => 'channel/PhutilChannel.php',
'PhutilChannelChannel' => 'channel/PhutilChannelChannel.php',
+ 'PhutilChannelTestCase' => 'channel/__tests__/PhutilChannelTestCase.php',
'PhutilChunkedIterator' => 'utils/PhutilChunkedIterator.php',
'PhutilChunkedIteratorTestCase' => 'utils/__tests__/PhutilChunkedIteratorTestCase.php',
'PhutilCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCodeSnippetContextFreeGrammar.php',
@@ -264,6 +264,8 @@
'PhutilRemarkupRuleItalic' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php',
'PhutilRemarkupRuleLinebreaks' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php',
'PhutilRemarkupRuleMonospace' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php',
+ 'PhutilRope' => 'utils/PhutilRope.php',
+ 'PhutilRopeTestCase' => 'utils/__tests__/PhutilRopeTestCase.php',
'PhutilSafeHTML' => 'markup/PhutilSafeHTML.php',
'PhutilSafeHTMLProducerInterface' => 'markup/PhutilSafeHTMLProducerInterface.php',
'PhutilSafeHTMLTestCase' => 'markup/__tests__/PhutilSafeHTMLTestCase.php',
@@ -363,6 +365,7 @@
'phutil_escape_uri' => 'markup/render.php',
'phutil_escape_uri_path_component' => 'markup/render.php',
'phutil_exit' => 'utils/utils.php',
+ 'phutil_fwrite_nonblocking_stream' => 'utils/utils.php',
'phutil_get_library_name_for_root' => 'moduleutils/moduleutils.php',
'phutil_get_library_root' => 'moduleutils/moduleutils.php',
'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php',
@@ -516,6 +519,7 @@
'PhutilCLikeCodeSnippetContextFreeGrammar' => 'PhutilCodeSnippetContextFreeGrammar',
'PhutilCallbackFilterIterator' => 'FilterIterator',
'PhutilChannelChannel' => 'PhutilChannel',
+ 'PhutilChannelTestCase' => 'PhutilTestCase',
'PhutilChunkedIterator' => 'Iterator',
'PhutilChunkedIteratorTestCase' => 'PhutilTestCase',
'PhutilCodeSnippetContextFreeGrammar' => 'PhutilContextFreeGrammar',
@@ -617,6 +621,8 @@
'PhutilRemarkupRuleItalic' => 'PhutilRemarkupRule',
'PhutilRemarkupRuleLinebreaks' => 'PhutilRemarkupRule',
'PhutilRemarkupRuleMonospace' => 'PhutilRemarkupRule',
+ 'PhutilRope' => 'Phobject',
+ 'PhutilRopeTestCase' => 'PhutilTestCase',
'PhutilSafeHTMLTestCase' => 'PhutilTestCase',
'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon',
'PhutilShellLexer' => 'PhutilLexer',
diff --git a/src/channel/PhutilChannel.php b/src/channel/PhutilChannel.php
--- a/src/channel/PhutilChannel.php
+++ b/src/channel/PhutilChannel.php
@@ -34,6 +34,7 @@
private $ibuf = '';
private $obuf;
private $name;
+ private $readBufferSize;
public function __construct() {
$this->obuf = new PhutilRope();
@@ -178,13 +179,19 @@
* @task update
*/
public function update() {
- while (true) {
- $in = $this->readBytes();
+ $maximum_read = PHP_INT_MAX;
+ if ($this->readBufferSize !== null) {
+ $maximum_read = ($this->readBufferSize - strlen($this->ibuf));
+ }
+
+ while ($maximum_read > 0) {
+ $in = $this->readBytes($maximum_read);
if (!strlen($in)) {
// Reading is blocked for now.
break;
}
$this->ibuf .= $in;
+ $maximum_read -= strlen($in);
}
while ($this->obuf->getByteLength()) {
@@ -275,11 +282,12 @@
/**
* Read from the channel's underlying I/O.
*
+ * @param int Maximum number of bytes to read.
* @return string Bytes, if available.
*
* @task impl
*/
- abstract protected function readBytes();
+ abstract protected function readBytes($length);
/**
@@ -318,6 +326,21 @@
/**
+ * Set the maximum size of the channel's read buffer. Reads will artificially
+ * block once the buffer reaches this size until the in-process buffer is
+ * consumed.
+ *
+ * @param int|null Maximum read buffer size, or `null` for a limitless buffer.
+ * @return this
+ * @task impl
+ */
+ public function setReadBufferSize($size) {
+ $this->readBufferSize = $size;
+ return $this;
+ }
+
+
+ /**
* Test state of the read buffer.
*
* @return bool True if the read buffer is empty.
diff --git a/src/channel/PhutilChannelChannel.php b/src/channel/PhutilChannelChannel.php
--- a/src/channel/PhutilChannelChannel.php
+++ b/src/channel/PhutilChannelChannel.php
@@ -53,7 +53,7 @@
return $this->channel->isOpenForWriting();
}
- protected function readBytes() {
+ protected function readBytes($length) {
$this->throwOnRawByteOperations();
}
@@ -69,6 +69,11 @@
return $this->channel->getWriteSockets();
}
+ public function setReadBufferSize($size) {
+ $this->channel->setReadBufferSize($size);
+ return $this;
+ }
+
public function isReadBufferEmpty() {
return $this->channel->isReadBufferEmpty();
}
diff --git a/src/channel/PhutilExecChannel.php b/src/channel/PhutilExecChannel.php
--- a/src/channel/PhutilExecChannel.php
+++ b/src/channel/PhutilExecChannel.php
@@ -90,7 +90,7 @@
return !$this->future->isReady();
}
- protected function readBytes() {
+ protected function readBytes($length) {
list($stdout, $stderr) = $this->future->read();
$this->future->discardBuffers();
@@ -126,6 +126,15 @@
return $this->future->getWriteSockets();
}
+ public function setReadBufferSize($size) {
+ // NOTE: We may end up using 2x the buffer size here, one inside
+ // ExecFuture and one inside the Channel. We could tune this eventually, but
+ // it should be fine for now.
+ parent::setReadBufferSize($size);
+ $this->future->setReadBufferSize($size);
+ return $this;
+ }
+
public function isWriteBufferEmpty() {
return $this->future->isWriteBufferEmpty();
}
diff --git a/src/channel/PhutilSocketChannel.php b/src/channel/PhutilSocketChannel.php
--- a/src/channel/PhutilSocketChannel.php
+++ b/src/channel/PhutilSocketChannel.php
@@ -103,13 +103,13 @@
return (bool)$this->writeSocket;
}
- protected function readBytes() {
+ protected function readBytes($length) {
$socket = $this->readSocket;
if (!$socket) {
return '';
}
- $data = @fread($socket, 4096);
+ $data = @fread($socket, min($length, 64 * 1024));
if ($data === false) {
$this->closeReadSocket();
diff --git a/src/channel/__tests__/PhutilChannelTestCase.php b/src/channel/__tests__/PhutilChannelTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/channel/__tests__/PhutilChannelTestCase.php
@@ -0,0 +1,45 @@
+<?php
+
+final class PhutilChannelTestCase extends PhutilTestCase {
+
+ public function testChannelBasics() {
+ list($x, $y) = PhutilSocketChannel::newChannelPair();
+
+ $str_len_8 = 'abcdefgh';
+ $str_len_4 = 'abcd';
+
+ // Do a write with no buffer limit.
+
+ $x->write($str_len_8);
+ while (true) {
+ $x->update();
+ $y->update();
+ $read = $y->read();
+ if (strlen($read)) {
+ break;
+ }
+ }
+
+ // We expect to read the entire message.
+ $this->assertEqual($str_len_8, $read);
+
+
+ // Again, with a read buffer limit.
+
+ $y->setReadBufferSize(4);
+ $x->write($str_len_8);
+
+ while (true) {
+ $x->update();
+ $y->update();
+ $read = $y->read();
+ if (strlen($read)) {
+ break;
+ }
+ }
+
+ // We expect to see only the first 4 bytes of the message.
+ $this->assertEqual($str_len_4, $read);
+ }
+
+}

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/nj/dd/ffyathk7qljvu66x
Default Alt Text
D7774.diff (8 KB)

Event Timeline