Skip to content

Commit

Permalink
feature #37519 [Process] allow setting options esp. "create_new_conso…
Browse files Browse the repository at this point in the history
…le" to detach a subprocess (andrei0x309)

This PR was merged into the 5.2-dev branch.

Discussion
----------

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

Process.php to include support for create_new_console (Windows ONLY)

| Q             | A
| ------------- | ---
| Branch?       | master <!-- see below -->
| Bug fix?      |  no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | #36583 <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

Based on this issue: symfony/symfony#36583
I created a quick repo [ https://github.com/andrei0x309/tets_sym_proc]( https://github.com/andrei0x309/tets_sym_proc) to illustrate
how this feature can be used, it essentially lets you run something even if your main script has terminated.

It is useful if you want to create something like
`new Process(['php', 'script_with_long_execution_time.php']);`
This was impossible to do on windows until the **create_new_console** flag was added in PHP 7.4.4.
With this feature Process can be used like this:

```
// Start a process and detach form it on Win only
$process = new Process(['php', 'worker.php']);
$process->setOptions(["create_new_console" => true]); // New method I added
$process->disableOutput();
$process->start();
Process Class behavior will never change if the user doesn't use setWinOptions()
```

Commits
-------

e9ab235b66 [Process] allow setting options esp. "create_new_console" to detach a subprocess
  • Loading branch information
fabpot committed Sep 8, 2020
2 parents bc88053 + a9bb93b commit 6beca1c
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 6beca1c

Please sign in to comment.