Page MenuHomePhabricator

D7772.id17578.diff
No OneTemporary

D7772.id17578.diff

Index: src/future/exec/ExecFuture.php
===================================================================
--- src/future/exec/ExecFuture.php
+++ src/future/exec/ExecFuture.php
@@ -39,6 +39,7 @@
protected $env = null;
protected $cwd;
+ private $readBufferSize;
protected $stdoutSizeLimit = PHP_INT_MAX;
protected $stderrSizeLimit = PHP_INT_MAX;
@@ -158,6 +159,25 @@
/**
+ * Set the maximum internal read buffer size this future. The future will
+ * block reads once the internal stdout or stderr buffer exceeds this size.
+ *
+ * NOTE: If you @{method:resolve} a future with a read buffer limit, you may
+ * block forever!
+ *
+ * TODO: We should probably release the read buffer limit during `resolve()`,
+ * or otherwise detect this. For now, be careful.
+ *
+ * @param int|null Maximum buffer size, or `null` for unlimited.
+ * @return this
+ */
+ public function setReadBufferSize($read_buffer_size) {
+ $this->readBufferSize = $read_buffer_size;
+ return $this;
+ }
+
+
+ /**
* Set the current working directory to use when executing the command.
*
* @param string Directory to set as CWD before executing the command.
@@ -507,14 +527,19 @@
* discarded.
* @param string Human-readable description of stream, for exception
* message.
+ * @param int Maximum number of bytes to read.
* @return string The data read from the stream.
* @task internal
*/
- protected function readAndDiscard($stream, $limit, $description) {
+ protected function readAndDiscard($stream, $limit, $description, $length) {
$output = '';
+ if ($length <= 0) {
+ return '';
+ }
+
do {
- $data = fread($stream, 4096);
+ $data = fread($stream, min($length, 64 * 1024));
if (false === $data) {
throw new Exception('Failed to read from '.$description);
}
@@ -528,6 +553,10 @@
$output .= $data;
$limit -= strlen($data);
}
+
+ if (strlen($output) >= $length) {
+ break;
+ }
} while ($read_bytes > 0);
return $output;
@@ -541,7 +570,6 @@
* @task internal
*/
public function isReady() {
-
// NOTE: We have soft dependencies on PhutilServiceProfiler and
// PhutilErrorTrap here. These depencies are soft to avoid the need to
// build them into the Phage agent. Under normal circumstances, these
@@ -646,14 +674,30 @@
// arrives between our last read and the process exiting.
$status = $this->procGetStatus();
- $this->stdout .= $this->readAndDiscard(
- $stdout,
- $this->getStdoutSizeLimit() - strlen($this->stdout),
- 'stdout');
- $this->stderr .= $this->readAndDiscard(
- $stderr,
- $this->getStderrSizeLimit() - strlen($this->stderr),
- 'stderr');
+ $read_buffer_size = $this->readBufferSize;
+
+ $read_stdout = PHP_INT_MAX;
+ $read_stderr = PHP_INT_MAX;
+ if ($read_buffer_size !== null) {
+ $read_stdout = max(0, ($read_buffer_size - strlen($this->stdout)));
+ $read_stderr = max(0, ($read_buffer_size - strlen($this->stderr)));
+ }
+
+ if ($read_stdout) {
+ $this->stdout .= $this->readAndDiscard(
+ $stdout,
+ $this->getStdoutSizeLimit() - strlen($this->stdout),
+ 'stdout',
+ $read_stdout);
+ }
+
+ if ($read_stderr) {
+ $this->stderr .= $this->readAndDiscard(
+ $stderr,
+ $this->getStderrSizeLimit() - strlen($this->stderr),
+ 'stderr',
+ $read_stderr);
+ }
if (!$status['running']) {
$this->result = array(
Index: src/future/exec/__tests__/ExecFutureTestCase.php
===================================================================
--- src/future/exec/__tests__/ExecFutureTestCase.php
+++ src/future/exec/__tests__/ExecFutureTestCase.php
@@ -121,4 +121,44 @@
$this->assertEqual(0, $err);
}
+ public function testReadBuffering() {
+ $str_len_8 = 'abcdefgh';
+ $str_len_4 = 'abcd';
+
+ // This is a write/read with no read buffer.
+ $future = new ExecFuture('cat');
+ $future->write($str_len_8);
+
+ do {
+ $future->isReady();
+ list($read) = $future->read();
+ if (strlen($read)) {
+ break;
+ }
+ } while (true);
+
+ // We expect to get the entire string back in the read.
+ $this->assertEqual($str_len_8, $read);
+ $future->resolve();
+
+
+ // This is a write/read with a read buffer.
+ $future = new ExecFuture('cat');
+ $future->write($str_len_8);
+
+ // Set the read buffer size.
+ $future->setReadBufferSize(4);
+ do {
+ $future->isReady();
+ list($read) = $future->read();
+ if (strlen($read)) {
+ break;
+ }
+ } while (true);
+
+ // We expect to get the entire string back in the read.
+ $this->assertEqual($str_len_4, $read);
+ $future->resolve();
+ }
+
}

File Metadata

Mime Type
text/plain
Expires
Tue, May 14, 7:56 PM (2 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6295596
Default Alt Text
D7772.id17578.diff (4 KB)

Event Timeline