Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15395458
D7772.id17588.diff
No One
Temporary
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.id17588.diff
View Options
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;
+
+ $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(
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
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 17, 7:08 AM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7353025
Default Alt Text
D7772.id17588.diff (4 KB)
Attached To
Mode
D7772: Add a maximum read buffer size to ExecFuture
Attached
Detach File
Event Timeline
Log In to Comment