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 @@ -45,6 +45,10 @@ private $profilerCallID; private $killedByTimeout; + private $useWindowsFileStreams = false; + private $windowsStdoutTempFile = null; + private $windowsStderrTempFile = null; + private static $descriptorSpec = array( 0 => array('pipe', 'r'), // stdin 1 => array('pipe', 'w'), // stdout @@ -229,6 +233,21 @@ } + /** + * Set whether to use non-blocking streams on Windows. + * + * @param bool Whether to use non-blocking streams. + * @return this + * @task config + */ + public function setUseWindowsFileStreams($use_streams) { + if (phutil_is_windows()) { + $this->useWindowsFileStreams = $use_streams; + } + return $this; + } + + /* -( Interacting With Commands )------------------------------------------ */ @@ -663,13 +682,47 @@ $trap = null; } + $spec = self::$descriptorSpec; + if ($this->useWindowsFileStreams) { + $this->windowsStdoutTempFile = new TempFile(); + $this->windowsStderrTempFile = new TempFile(); + + $spec = array( + 0 => self::$descriptorSpec[0], // stdin + 1 => fopen($this->windowsStdoutTempFile, 'wb'), // stdout + 2 => fopen($this->windowsStderrTempFile, 'wb'), // stderr + ); + + if (!$spec[1] || !$spec[2]) { + throw new Exception(pht( + 'Unable to create temporary files for '. + 'Windows stdout / stderr streams')); + } + } + $proc = @proc_open( $unmasked_command, - self::$descriptorSpec, + $spec, $pipes, $cwd, $env); + if ($this->useWindowsFileStreams) { + fclose($spec[1]); + fclose($spec[2]); + $pipes = array( + 0 => head($pipes), // stdin + 1 => fopen($this->windowsStdoutTempFile, 'rb'), // stdout + 2 => fopen($this->windowsStderrTempFile, 'rb'), // stderr + ); + + if (!$pipes[1] || !$pipes[2]) { + throw new Exception(pht( + 'Unable to open temporary files for '. + 'reading Windows stdout / stderr streams')); + } + } + if ($trap) { $err = $trap->getErrorsAsString(); $trap->destroy(); @@ -688,9 +741,9 @@ if (!phutil_is_windows()) { - // On Windows, there's no such thing as nonblocking interprocess I/O. - // Just leave the sockets blocking and hope for the best. Some features - // will not work. + // On Windows, we redirect process standard output and standard error + // through temporary files, and then use stream_select to determine + // if there's more data to read. if ((!stream_set_blocking($stdout, false)) || (!stream_set_blocking($stderr, false)) || @@ -757,6 +810,11 @@ } if (!$status['running']) { + if ($this->useWindowsFileStreams) { + fclose($stdout); + fclose($stderr); + } + $this->result = array( $status['exitcode'], $this->stdout,