Skip to content

Commit

Permalink
[Process] allow setting options esp. "create_new_console" to detach a…
Browse files Browse the repository at this point in the history
… subprocess
  • Loading branch information
andrei0x309 authored and nicolas-grekas committed Sep 8, 2020
1 parent d158a45 commit a9bb93b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
CHANGELOG
=========

5.2.0
-----

* added `Process::setOptions()` to set `Process` specific options
* added option `create_new_console` to allow a subprocess to continue
to run after the main script exited, both on Linux and on Windows

5.1.0
-----

Expand Down
38 changes: 33 additions & 5 deletions Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Process implements \IteratorAggregate
private $incrementalErrorOutputOffset = 0;
private $tty = false;
private $pty;
private $options = ['suppress_errors' => true, 'bypass_shell' => true];

private $useFileHandles = false;
/** @var PipesInterface */
Expand Down Expand Up @@ -196,7 +197,11 @@ public static function fromShellCommandline(string $command, string $cwd = null,

public function __destruct()
{
$this->stop(0);
if ($this->options['create_new_console'] ?? false) {
$this->processPipes->close();
} else {
$this->stop(0);
}
}

public function __clone()
Expand Down Expand Up @@ -303,10 +308,7 @@ public function start(callable $callback = null, array $env = [])
$commandline = $this->replacePlaceholders($commandline, $env);
}

$options = ['suppress_errors' => true];

if ('\\' === \DIRECTORY_SEPARATOR) {
$options['bypass_shell'] = true;
$commandline = $this->prepareWindowsCommandLine($commandline, $env);
} elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
Expand All @@ -332,7 +334,7 @@ public function start(callable $callback = null, array $env = [])
throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
}

$this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
$this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);

if (!\is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
Expand Down Expand Up @@ -1220,6 +1222,32 @@ public function getStartTime(): float
return $this->starttime;
}

/**
* Defines options to pass to the underlying proc_open().
*
* @see https://php.net/proc_open for the options supported by PHP.
*
* Enabling the "create_new_console" option allows a subprocess to continue
* to run after the main process exited, on both Windows and *nix
*/
public function setOptions(array $options)
{
if ($this->isRunning()) {
throw new RuntimeException('Setting options while the process is running is not possible.');
}

$defaultOptions = $this->options;
$existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];

foreach ($options as $key => $value) {
if (!\in_array($key, $existingOptions)) {
$this->options = $defaultOptions;
throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
}
$this->options[$key] = $value;
}
}

/**
* Returns whether TTY is supported on the current operating system.
*/
Expand Down
45 changes: 45 additions & 0 deletions Tests/CreateNewConsoleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Process\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Process;

/**
* @author Andrei Olteanu <[email protected]>
*/
class CreateNewConsoleTest extends TestCase
{
public function testOptionCreateNewConsole()
{
$this->expectNotToPerformAssertions();
try {
$process = new Process(['php', __DIR__.'/ThreeSecondProcess.php']);
$process->setOptions(['create_new_console' => true]);
$process->disableOutput();
$process->start();
} catch (\Exception $e) {
$this->fail($e);
}
}

public function testItReturnsFastAfterStart()
{
// The started process must run in background after the main has finished but that can't be tested with PHPUnit
$startTime = microtime(true);
$process = new Process(['php', __DIR__.'/ThreeSecondProcess.php']);
$process->setOptions(['create_new_console' => true]);
$process->disableOutput();
$process->start();
$this->assertLessThan(3000, $startTime - microtime(true));
}
}
14 changes: 14 additions & 0 deletions Tests/ThreeSecondProcess.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

echo 'Worker started';
sleep(3);
echo 'Worker done';

0 comments on commit a9bb93b

Please sign in to comment.