[Process] Fix #8739
This commit is contained in:
parent
7716fb25ab
commit
3ef517b356
@ -38,6 +38,7 @@ class Process
|
|||||||
// Timeout Precision in seconds.
|
// Timeout Precision in seconds.
|
||||||
const TIMEOUT_PRECISION = 0.2;
|
const TIMEOUT_PRECISION = 0.2;
|
||||||
|
|
||||||
|
private $callback;
|
||||||
private $commandline;
|
private $commandline;
|
||||||
private $cwd;
|
private $cwd;
|
||||||
private $env;
|
private $env;
|
||||||
@ -164,6 +165,7 @@ class Process
|
|||||||
|
|
||||||
public function __clone()
|
public function __clone()
|
||||||
{
|
{
|
||||||
|
$this->callback = null;
|
||||||
$this->exitcode = null;
|
$this->exitcode = null;
|
||||||
$this->fallbackExitcode = null;
|
$this->fallbackExitcode = null;
|
||||||
$this->processInformation = null;
|
$this->processInformation = null;
|
||||||
@ -199,7 +201,7 @@ class Process
|
|||||||
{
|
{
|
||||||
$this->start($callback);
|
$this->start($callback);
|
||||||
|
|
||||||
return $this->wait($callback);
|
return $this->wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,7 +236,7 @@ class Process
|
|||||||
$this->stderr = '';
|
$this->stderr = '';
|
||||||
$this->incrementalOutputOffset = 0;
|
$this->incrementalOutputOffset = 0;
|
||||||
$this->incrementalErrorOutputOffset = 0;
|
$this->incrementalErrorOutputOffset = 0;
|
||||||
$callback = $this->buildCallback($callback);
|
$this->callback = $this->buildCallback($callback);
|
||||||
|
|
||||||
//Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
|
//Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
|
||||||
//Workaround for this problem is to use temporary files instead of pipes on Windows platform.
|
//Workaround for this problem is to use temporary files instead of pipes on Windows platform.
|
||||||
@ -285,69 +287,11 @@ class Process
|
|||||||
stream_set_blocking($pipe, false);
|
stream_set_blocking($pipe, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $this->stdin) {
|
$this->writePipes(false);
|
||||||
fclose($this->pipes[0]);
|
$this->updateStatus(false);
|
||||||
unset($this->pipes[0]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$writePipes = array($this->pipes[0]);
|
|
||||||
unset($this->pipes[0]);
|
|
||||||
$stdinLen = strlen($this->stdin);
|
|
||||||
$stdinOffset = 0;
|
|
||||||
|
|
||||||
while ($writePipes) {
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
|
||||||
$this->processFileHandles($callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
$r = $this->pipes;
|
|
||||||
$w = $writePipes;
|
|
||||||
$e = null;
|
|
||||||
|
|
||||||
if (false === $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6))) {
|
|
||||||
// if a system call has been interrupted, forget about it, let's try again
|
|
||||||
if ($this->hasSystemCallBeenInterrupted()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing has changed, let's wait until the process is ready
|
|
||||||
if (0 === $n) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($w) {
|
|
||||||
$written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
|
|
||||||
if (false !== $written) {
|
|
||||||
$stdinOffset += $written;
|
|
||||||
}
|
|
||||||
if ($stdinOffset >= $stdinLen) {
|
|
||||||
fclose($writePipes[0]);
|
|
||||||
$writePipes = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($r as $pipe) {
|
|
||||||
$type = array_search($pipe, $this->pipes);
|
|
||||||
$data = fread($pipe, 8192);
|
|
||||||
if (strlen($data) > 0) {
|
|
||||||
call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
|
|
||||||
}
|
|
||||||
if (false === $data || feof($pipe)) {
|
|
||||||
fclose($pipe);
|
|
||||||
unset($this->pipes[$type]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->checkTimeout();
|
$this->checkTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updateStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restarts the process.
|
* Restarts the process.
|
||||||
*
|
*
|
||||||
@ -391,55 +335,15 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function wait($callback = null)
|
public function wait($callback = null)
|
||||||
{
|
{
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
$callback = $this->buildCallback($callback);
|
if (null !== $callback) {
|
||||||
while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) {
|
$this->callback = $this->buildCallback($callback);
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
|
|
||||||
$this->processFileHandles($callback, !$this->pipes);
|
|
||||||
}
|
}
|
||||||
|
while ($this->processInformation['running']) {
|
||||||
|
$this->updateStatus(true);
|
||||||
$this->checkTimeout();
|
$this->checkTimeout();
|
||||||
|
|
||||||
if ($this->pipes) {
|
|
||||||
$r = $this->pipes;
|
|
||||||
$w = null;
|
|
||||||
$e = null;
|
|
||||||
|
|
||||||
// let's have a look if something changed in streams
|
|
||||||
if (false === $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6))) {
|
|
||||||
// if a system call has been interrupted, forget about it, let's try again
|
|
||||||
// otherwise, an error occured, let's reset pipes
|
|
||||||
if (!$this->hasSystemCallBeenInterrupted()) {
|
|
||||||
$this->pipes = array();
|
|
||||||
}
|
}
|
||||||
|
$this->updateStatus(false);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing has changed
|
|
||||||
if (0 === $n) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($r as $pipe) {
|
|
||||||
$type = array_search($pipe, $this->pipes);
|
|
||||||
$data = fread($pipe, 8192);
|
|
||||||
|
|
||||||
if (strlen($data) > 0) {
|
|
||||||
// last exit code is output and caught to work around --enable-sigchild
|
|
||||||
if (3 == $type) {
|
|
||||||
$this->fallbackExitcode = (int) $data;
|
|
||||||
} else {
|
|
||||||
call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (false === $data || feof($pipe)) {
|
|
||||||
fclose($pipe);
|
|
||||||
unset($this->pipes[$type]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->updateStatus();
|
|
||||||
if ($this->processInformation['signaled']) {
|
if ($this->processInformation['signaled']) {
|
||||||
if ($this->isSigchildEnabled()) {
|
if ($this->isSigchildEnabled()) {
|
||||||
throw new RuntimeException('The process has been signaled.');
|
throw new RuntimeException('The process has been signaled.');
|
||||||
@ -482,7 +386,7 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function getOutput()
|
public function getOutput()
|
||||||
{
|
{
|
||||||
$this->updateOutput();
|
$this->readPipes(false);
|
||||||
|
|
||||||
return $this->stdout;
|
return $this->stdout;
|
||||||
}
|
}
|
||||||
@ -514,7 +418,7 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function getErrorOutput()
|
public function getErrorOutput()
|
||||||
{
|
{
|
||||||
$this->updateErrorOutput();
|
$this->readPipes(false);
|
||||||
|
|
||||||
return $this->stderr;
|
return $this->stderr;
|
||||||
}
|
}
|
||||||
@ -553,7 +457,7 @@ class Process
|
|||||||
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method');
|
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->exitcode;
|
return $this->exitcode;
|
||||||
}
|
}
|
||||||
@ -605,7 +509,7 @@ class Process
|
|||||||
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
|
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->processInformation['signaled'];
|
return $this->processInformation['signaled'];
|
||||||
}
|
}
|
||||||
@ -627,7 +531,7 @@ class Process
|
|||||||
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
|
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->processInformation['termsig'];
|
return $this->processInformation['termsig'];
|
||||||
}
|
}
|
||||||
@ -643,7 +547,7 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function hasBeenStopped()
|
public function hasBeenStopped()
|
||||||
{
|
{
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->processInformation['stopped'];
|
return $this->processInformation['stopped'];
|
||||||
}
|
}
|
||||||
@ -659,7 +563,7 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function getStopSignal()
|
public function getStopSignal()
|
||||||
{
|
{
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->processInformation['stopsig'];
|
return $this->processInformation['stopsig'];
|
||||||
}
|
}
|
||||||
@ -675,7 +579,7 @@ class Process
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->processInformation['running'];
|
return $this->processInformation['running'];
|
||||||
}
|
}
|
||||||
@ -697,7 +601,7 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function isTerminated()
|
public function isTerminated()
|
||||||
{
|
{
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->status == self::STATUS_TERMINATED;
|
return $this->status == self::STATUS_TERMINATED;
|
||||||
}
|
}
|
||||||
@ -711,7 +615,7 @@ class Process
|
|||||||
*/
|
*/
|
||||||
public function getStatus()
|
public function getStatus()
|
||||||
{
|
{
|
||||||
$this->updateStatus();
|
$this->updateStatus(false);
|
||||||
|
|
||||||
return $this->status;
|
return $this->status;
|
||||||
}
|
}
|
||||||
@ -1062,14 +966,18 @@ class Process
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the status of the process.
|
* Updates the status of the process, reads pipes.
|
||||||
|
*
|
||||||
|
* @param Boolean $blocking Whether to use a clocking read call.
|
||||||
*/
|
*/
|
||||||
protected function updateStatus()
|
protected function updateStatus($blocking)
|
||||||
{
|
{
|
||||||
if (self::STATUS_STARTED !== $this->status) {
|
if (self::STATUS_STARTED !== $this->status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->readPipes($blocking);
|
||||||
|
|
||||||
$this->processInformation = proc_get_status($this->process);
|
$this->processInformation = proc_get_status($this->process);
|
||||||
if (!$this->processInformation['running']) {
|
if (!$this->processInformation['running']) {
|
||||||
$this->status = self::STATUS_TERMINATED;
|
$this->status = self::STATUS_TERMINATED;
|
||||||
@ -1079,29 +987,6 @@ class Process
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the current error output of the process (STDERR).
|
|
||||||
*/
|
|
||||||
protected function updateErrorOutput()
|
|
||||||
{
|
|
||||||
if (isset($this->pipes[self::STDERR]) && is_resource($this->pipes[self::STDERR])) {
|
|
||||||
$this->addErrorOutput(stream_get_contents($this->pipes[self::STDERR]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the current output of the process (STDOUT).
|
|
||||||
*/
|
|
||||||
protected function updateOutput()
|
|
||||||
{
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($this->fileHandles[self::STDOUT]) && is_resource($this->fileHandles[self::STDOUT])) {
|
|
||||||
fseek($this->fileHandles[self::STDOUT], $this->readBytes[self::STDOUT]);
|
|
||||||
$this->addOutput(stream_get_contents($this->fileHandles[self::STDOUT]));
|
|
||||||
} elseif (isset($this->pipes[self::STDOUT]) && is_resource($this->pipes[self::STDOUT])) {
|
|
||||||
$this->addOutput(stream_get_contents($this->pipes[self::STDOUT]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether PHP has been compiled with the '--enable-sigchild' option or not
|
* Return whether PHP has been compiled with the '--enable-sigchild' option or not
|
||||||
*
|
*
|
||||||
@ -1122,10 +1007,9 @@ class Process
|
|||||||
/**
|
/**
|
||||||
* Handles the windows file handles fallbacks
|
* Handles the windows file handles fallbacks
|
||||||
*
|
*
|
||||||
* @param callable $callback A valid PHP callback
|
|
||||||
* @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
|
* @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
|
||||||
*/
|
*/
|
||||||
private function processFileHandles($callback, $closeEmptyHandles = false)
|
private function processFileHandles($closeEmptyHandles = false)
|
||||||
{
|
{
|
||||||
$fh = $this->fileHandles;
|
$fh = $this->fileHandles;
|
||||||
foreach ($fh as $type => $fileHandle) {
|
foreach ($fh as $type => $fileHandle) {
|
||||||
@ -1133,7 +1017,7 @@ class Process
|
|||||||
$data = fread($fileHandle, 8192);
|
$data = fread($fileHandle, 8192);
|
||||||
if (strlen($data) > 0) {
|
if (strlen($data) > 0) {
|
||||||
$this->readBytes[$type] += strlen($data);
|
$this->readBytes[$type] += strlen($data);
|
||||||
call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
|
call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
|
||||||
}
|
}
|
||||||
if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
|
if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
|
||||||
fclose($fileHandle);
|
fclose($fileHandle);
|
||||||
@ -1154,4 +1038,106 @@ class Process
|
|||||||
// stream_select returns false when the `select` system call is interrupted by an incoming signal
|
// stream_select returns false when the `select` system call is interrupted by an incoming signal
|
||||||
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
|
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads pipes, executes callback.
|
||||||
|
*
|
||||||
|
* @param Boolean $blocking Whether to use blocking calls or not.
|
||||||
|
*/
|
||||||
|
private function readPipes($blocking)
|
||||||
|
{
|
||||||
|
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
|
||||||
|
$this->processFileHandles(!$this->pipes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->pipes) {
|
||||||
|
$r = $this->pipes;
|
||||||
|
$w = null;
|
||||||
|
$e = null;
|
||||||
|
|
||||||
|
// let's have a look if something changed in streams
|
||||||
|
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(self::TIMEOUT_PRECISION * 1E6) : 0)) {
|
||||||
|
// if a system call has been interrupted, forget about it, let's try again
|
||||||
|
// otherwise, an error occured, let's reset pipes
|
||||||
|
if (!$this->hasSystemCallBeenInterrupted()) {
|
||||||
|
$this->pipes = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing has changed
|
||||||
|
if (0 === $n) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($r as $pipe) {
|
||||||
|
$type = array_search($pipe, $this->pipes);
|
||||||
|
$data = fread($pipe, 8192);
|
||||||
|
|
||||||
|
if (strlen($data) > 0) {
|
||||||
|
// last exit code is output and caught to work around --enable-sigchild
|
||||||
|
if (3 == $type) {
|
||||||
|
$this->fallbackExitcode = (int) $data;
|
||||||
|
} else {
|
||||||
|
call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (false === $data || feof($pipe)) {
|
||||||
|
fclose($pipe);
|
||||||
|
unset($this->pipes[$type]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to pipes.
|
||||||
|
*
|
||||||
|
* @param Boolean $blocking Whether to use blocking calls or not.
|
||||||
|
*/
|
||||||
|
private function writePipes($blocking)
|
||||||
|
{
|
||||||
|
if (null === $this->stdin) {
|
||||||
|
fclose($this->pipes[0]);
|
||||||
|
unset($this->pipes[0]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$writePipes = array($this->pipes[0]);
|
||||||
|
unset($this->pipes[0]);
|
||||||
|
$stdinLen = strlen($this->stdin);
|
||||||
|
$stdinOffset = 0;
|
||||||
|
|
||||||
|
while ($writePipes) {
|
||||||
|
$r = array();
|
||||||
|
$w = $writePipes;
|
||||||
|
$e = null;
|
||||||
|
|
||||||
|
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(static::TIMEOUT_PRECISION * 1E6) : 0)) {
|
||||||
|
// if a system call has been interrupted, forget about it, let's try again
|
||||||
|
if ($this->hasSystemCallBeenInterrupted()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing has changed, let's wait until the process is ready
|
||||||
|
if (0 === $n) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($w) {
|
||||||
|
$written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
|
||||||
|
if (false !== $written) {
|
||||||
|
$stdinOffset += $written;
|
||||||
|
}
|
||||||
|
if ($stdinOffset >= $stdinLen) {
|
||||||
|
fclose($writePipes[0]);
|
||||||
|
$writePipes = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user