[Process] Make Process::start non-blocking on Windows platform
This commit is contained in:
parent
02088bc62f
commit
1f5bf324fe
@ -232,7 +232,11 @@ class Process
|
|||||||
$commandline = $this->commandline;
|
$commandline = $this->commandline;
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
|
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
|
||||||
$commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"';
|
$commandline = 'cmd /V:ON /E:ON /C "('.$commandline.')"';
|
||||||
|
foreach ($this->processPipes->getFiles() as $offset => $filename) {
|
||||||
|
$commandline .= ' '.$offset.'>'.$filename;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset($this->options['bypass_shell'])) {
|
if (!isset($this->options['bypass_shell'])) {
|
||||||
$this->options['bypass_shell'] = true;
|
$this->options['bypass_shell'] = true;
|
||||||
}
|
}
|
||||||
@ -618,6 +622,12 @@ class Process
|
|||||||
{
|
{
|
||||||
$timeoutMicro = microtime(true) + $timeout;
|
$timeoutMicro = microtime(true) + $timeout;
|
||||||
if ($this->isRunning()) {
|
if ($this->isRunning()) {
|
||||||
|
if (defined('PHP_WINDOWS_VERSION_BUILD') && !$this->isSigchildEnabled()) {
|
||||||
|
exec(sprintf("taskkill /F /T /PID %d 2>&1", $this->getPid()), $output, $exitCode);
|
||||||
|
if ($exitCode > 0) {
|
||||||
|
throw new RuntimeException('Unable to kill the process');
|
||||||
|
}
|
||||||
|
}
|
||||||
proc_terminate($this->process);
|
proc_terminate($this->process);
|
||||||
do {
|
do {
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
|
@ -21,6 +21,8 @@ class ProcessPipes
|
|||||||
/** @var array */
|
/** @var array */
|
||||||
public $pipes = array();
|
public $pipes = array();
|
||||||
/** @var array */
|
/** @var array */
|
||||||
|
private $files = array();
|
||||||
|
/** @var array */
|
||||||
private $fileHandles = array();
|
private $fileHandles = array();
|
||||||
/** @var array */
|
/** @var array */
|
||||||
private $readBytes = array();
|
private $readBytes = array();
|
||||||
@ -39,20 +41,21 @@ class ProcessPipes
|
|||||||
// 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.
|
||||||
//
|
//
|
||||||
// Please note that this work around prevents hanging but
|
|
||||||
// another issue occurs : In some race conditions, some data may be
|
|
||||||
// lost or corrupted.
|
|
||||||
//
|
|
||||||
// @see https://bugs.php.net/bug.php?id=51800
|
// @see https://bugs.php.net/bug.php?id=51800
|
||||||
if ($this->useFiles) {
|
if ($this->useFiles) {
|
||||||
$this->fileHandles = array(
|
$this->files = array(
|
||||||
Process::STDOUT => tmpfile(),
|
Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'),
|
||||||
|
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'),
|
||||||
);
|
);
|
||||||
if (false === $this->fileHandles[Process::STDOUT]) {
|
foreach ($this->files as $offset => $file) {
|
||||||
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
|
$this->fileHandles[$offset] = fopen($this->files[$offset], 'rb');
|
||||||
|
if (false === $this->fileHandles[$offset]) {
|
||||||
|
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->readBytes = array(
|
$this->readBytes = array(
|
||||||
Process::STDOUT => 0,
|
Process::STDOUT => 0,
|
||||||
|
Process::STDERR => 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +63,7 @@ class ProcessPipes
|
|||||||
public function __destruct()
|
public function __destruct()
|
||||||
{
|
{
|
||||||
$this->close();
|
$this->close();
|
||||||
|
$this->removeFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,11 +109,13 @@ class ProcessPipes
|
|||||||
public function getDescriptors()
|
public function getDescriptors()
|
||||||
{
|
{
|
||||||
if ($this->useFiles) {
|
if ($this->useFiles) {
|
||||||
|
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
|
||||||
|
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
|
||||||
|
// So we redirect output within the commandline and pass the nul device to the process
|
||||||
return array(
|
return array(
|
||||||
array('pipe', 'r'),
|
array('pipe', 'r'),
|
||||||
$this->fileHandles[Process::STDOUT],
|
array('file', 'NUL', 'w'),
|
||||||
// Use a file handle only for STDOUT. Using for both STDOUT and STDERR would trigger https://bugs.php.net/bug.php?id=65650
|
array('file', 'NUL', 'w'),
|
||||||
array('pipe', 'w'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +134,20 @@ class ProcessPipes
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFiles()
|
||||||
|
{
|
||||||
|
if ($this->useFiles) {
|
||||||
|
return $this->files;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads data in file handles and pipes.
|
* Reads data in file handles and pipes.
|
||||||
*
|
*
|
||||||
@ -322,4 +342,17 @@ class ProcessPipes
|
|||||||
// 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes temporary files
|
||||||
|
*/
|
||||||
|
private function removeFiles()
|
||||||
|
{
|
||||||
|
foreach ($this->files as $filename) {
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
@unlink($filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->files = array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,14 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
|
|||||||
$this->markTestSkipped('Signal is not supported in sigchild environment');
|
$this->markTestSkipped('Signal is not supported in sigchild environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testStartAfterATimeout()
|
||||||
|
{
|
||||||
|
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||||
|
$this->markTestSkipped('Restarting a timed-out process on Windows is not supported in sigchild environment');
|
||||||
|
}
|
||||||
|
parent::testStartAfterATimeout();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user