Page MenuHomePhabricator

D7768.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,6 +15,7 @@
'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',
diff --git a/src/future/exec/ExecFuture.php b/src/future/exec/ExecFuture.php
--- a/src/future/exec/ExecFuture.php
+++ b/src/future/exec/ExecFuture.php
@@ -69,6 +69,7 @@
public function __construct($command) {
$argv = func_get_args();
$this->command = call_user_func_array('csprintf', $argv);
+ $this->stdin = new PhutilRope();
}
@@ -275,7 +276,12 @@
* @task interact
*/
public function write($data, $keep_pipe = false) {
- $this->stdin .= $data;
+ if (strlen($data)) {
+ if (!$this->stdin) {
+ throw new Exception(pht('Writing to a closed pipe!'));
+ }
+ $this->stdin->append($data);
+ }
$this->closePipe = !$keep_pipe;
return $this;
@@ -473,7 +479,7 @@
public function getWriteSockets() {
list($stdin, $stdout, $stderr) = $this->pipes;
$sockets = array();
- if (isset($stdin) && strlen($this->stdin) && !feof($stdin)) {
+ if (isset($stdin) && $this->stdin->getByteLength() && !feof($stdin)) {
$sockets[] = $stdin;
}
return $sockets;
@@ -609,19 +615,24 @@
list($stdin, $stdout, $stderr) = $this->pipes;
- if (isset($this->stdin) && strlen($this->stdin)) {
- $bytes = fwrite($stdin, $this->stdin);
+ while (isset($this->stdin) && $this->stdin->getByteLength()) {
+ $write_segment = $this->stdin->getAnyPrefix();
+
+ $bytes = fwrite($stdin, $write_segment);
if ($bytes === false) {
throw new Exception('Unable to write to stdin!');
} else if ($bytes) {
- $this->stdin = substr($this->stdin, $bytes);
+ $this->stdin->removeBytesFromHead($bytes);
+ } else {
+ // Writes are blocked for now.
+ break;
}
}
$this->tryToCloseStdin();
- // Read status before reading pipes so that we can never miss data that
- // arrives between our last read and the process exiting.
+ // Read status before reading pipes so that we can never miss data that
+ // arrives between our last read and the process exiting.
$status = $this->procGetStatus();
$this->stdout .= $this->readAndDiscard(
@@ -737,7 +748,7 @@
return;
}
- if (strlen($this->stdin)) {
+ if ($this->stdin->getByteLength()) {
// We still have bytes to write.
return;
}
diff --git a/src/utils/PhutilRope.php b/src/utils/PhutilRope.php
new file mode 100644
--- /dev/null
+++ b/src/utils/PhutilRope.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * String-like object which reduces the cost of managing large strings. This
+ * is particularly useful for buffering large amounts of data that is being
+ * passed to `fwrite()`.
+ *
+ * @group util
+ */
+final class PhutilRope extends Phobject {
+
+ private $length = 0;
+ private $buffers = array();
+ private $segmentSize = 16384;
+
+ /**
+ * Append a string to the rope.
+ *
+ * @param string String to append.
+ * @return this
+ */
+ public function append($string) {
+ if (!strlen($string)) {
+ return $this;
+ }
+
+ $len = strlen($string);
+ $this->length += $len;
+
+ if ($len <= $this->segmentSize) {
+ $this->buffers[] = $string;
+ } else {
+ for ($cursor = 0; $cursor < $len; $cursor += $this->segmentSize) {
+ $this->buffers[] = substr($string, $cursor, $this->segmentSize);
+ }
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Get the length of the rope.
+ *
+ * @return int Length of the rope in bytes.
+ */
+ public function getByteLength() {
+ return $this->length;
+ }
+
+
+ /**
+ * Get an arbitrary, nonempty prefix of the rope.
+ *
+ * @return string Some rope prefix.
+ */
+ public function getAnyPrefix() {
+ $result = reset($this->buffers);
+ if ($result === false) {
+ return null;
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Return the entire rope as a normal string.
+ *
+ * @return string Normal string.
+ */
+ public function getAsString() {
+ return implode('', $this->buffers);
+ }
+
+
+ /**
+ * Remove a specified number of bytes from the head of the rope.
+ *
+ * @param int Bytes to remove.
+ * @return this
+ */
+ public function removeBytesFromHead($length) {
+ if ($length <= 0) {
+ throw new InvalidArgumentException(
+ pht('Length must be larger than 0!'));
+ }
+
+ $remaining_length = $length;
+ foreach ($this->buffers as $key => $buf) {
+ $len = strlen($buf);
+ if ($len <= $length) {
+ unset($this->buffers[$key]);
+ $remaining_length -= $len;
+ if (!$remaining_length) {
+ break;
+ }
+ } else {
+ $this->buffers[$key] = substr($buf, $length);
+ break;
+ }
+ }
+
+ if ($length <= $this->length) {
+ $this->length -= $length;
+ } else {
+ $this->length = 0;
+ }
+
+ return $this;
+ }
+
+}
diff --git a/src/utils/__tests__/PhutilRopeTestCase.php b/src/utils/__tests__/PhutilRopeTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/utils/__tests__/PhutilRopeTestCase.php
@@ -0,0 +1,24 @@
+<?php
+
+final class PhutilRopeTestCase extends PhutilTestCase {
+
+ public function testRopeOperations() {
+ $rope = new PhutilRope();
+ $rope->append('aaa');
+ $rope->append('bbb');
+
+ $this->assertEqual(6, $rope->getByteLength());
+ $this->assertEqual('aaabbb', $rope->getAsString());
+
+ $rope->removeBytesFromHead(2);
+
+ $this->assertEqual(4, $rope->getByteLength());
+ $this->assertEqual('abbb', $rope->getAsString());
+
+ $rope->removeBytesFromHead(4);
+
+ $this->assertEqual(0, $rope->getByteLength());
+ $this->assertEqual('', $rope->getAsString());
+ }
+
+}

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/rq/l6/kihhepf4ooyu4sot
Default Alt Text
D7768.diff (6 KB)

Event Timeline