[Process] Make Process::start non-blocking on Windows platform

This commit is contained in:
Romain Neutron 2014-03-11 17:05:18 +01:00
parent 02088bc62f
commit 1f5bf324fe
3 changed files with 63 additions and 12 deletions

View File

@ -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);

View File

@ -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();
}
} }

View File

@ -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}
*/ */