Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F90524
D7772.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Referenced Files
None
Subscribers
None
D7772.diff
View Options
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
@@ -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;
+
+ $max_stdout_read_bytes = PHP_INT_MAX;
+ $max_stderr_read_bytes = PHP_INT_MAX;
+ if ($read_buffer_size !== null) {
+ $max_stdout_read_bytes = $read_buffer_size - strlen($this->stdout);
+ $max_stderr_read_bytes = $read_buffer_size - strlen($this->stderr);
+ }
+
+ if ($max_stdout_read_bytes > 0) {
+ $this->stdout .= $this->readAndDiscard(
+ $stdout,
+ $this->getStdoutSizeLimit() - strlen($this->stdout),
+ 'stdout',
+ $max_stdout_read_bytes);
+ }
+
+ if ($max_stderr_read_bytes > 0) {
+ $this->stderr .= $this->readAndDiscard(
+ $stderr,
+ $this->getStderrSizeLimit() - strlen($this->stderr),
+ 'stderr',
+ $max_stderr_read_bytes);
+ }
if (!$status['running']) {
$this->result = array(
diff --git a/src/future/exec/__tests__/ExecFutureTestCase.php b/src/future/exec/__tests__/ExecFutureTestCase.php
--- a/src/future/exec/__tests__/ExecFutureTestCase.php
+++ b/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
Details
Attached
Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/h4/yg/lmdht3ii2q56trjz
Default Alt Text
D7772.diff (4 KB)
Attached To
Mode
D7772: Add a maximum read buffer size to ExecFuture
Attached
Detach File
Event Timeline
Log In to Comment