diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 33cb381397..4fa444cace 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -232,7 +232,11 @@ class Process $commandline = $this->commandline; 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'])) { $this->options['bypass_shell'] = true; } @@ -618,6 +622,12 @@ class Process { $timeoutMicro = microtime(true) + $timeout; 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); do { usleep(1000); diff --git a/src/Symfony/Component/Process/ProcessPipes.php b/src/Symfony/Component/Process/ProcessPipes.php index d1b434ebf8..e6aabc5f99 100644 --- a/src/Symfony/Component/Process/ProcessPipes.php +++ b/src/Symfony/Component/Process/ProcessPipes.php @@ -21,6 +21,8 @@ class ProcessPipes /** @var array */ public $pipes = array(); /** @var array */ + private $files = array(); + /** @var array */ private $fileHandles = array(); /** @var 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. // 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 if ($this->useFiles) { - $this->fileHandles = array( - Process::STDOUT => tmpfile(), + $this->files = array( + 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]) { - throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + foreach ($this->files as $offset => $file) { + $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( Process::STDOUT => 0, + Process::STDERR => 0, ); } } @@ -60,6 +63,7 @@ class ProcessPipes public function __destruct() { $this->close(); + $this->removeFiles(); } /** @@ -105,11 +109,13 @@ class ProcessPipes public function getDescriptors() { 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( array('pipe', 'r'), - $this->fileHandles[Process::STDOUT], - // Use a file handle only for STDOUT. Using for both STDOUT and STDERR would trigger https://bugs.php.net/bug.php?id=65650 - array('pipe', 'w'), + array('file', 'NUL', 'w'), + array('file', 'NUL', '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. * @@ -322,4 +342,17 @@ class ProcessPipes // 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'); } + + /** + * Removes temporary files + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = array(); + } } diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php index 524cc66374..65dd4bb573 100644 --- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php @@ -112,6 +112,14 @@ class SigchildEnabledProcessTest extends AbstractProcessTest $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} */