Skip to content

[Process] allow setting options esp. "create_new_console" to detach a subprocess #37519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Symfony/Component/Process/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 src/Symfony/Component/Process/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 src/Symfony/Component/Process/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 <fabien@symfony.com>
*
* 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 <andrei@flashsoft.eu>
*/
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 src/Symfony/Component/Process/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 <fabien@symfony.com>
*
* 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';