feature #27742 [Process] Add feature "wait until callback" to process class (Nek-)
This PR was squashed before being merged into the 4.2-dev branch (closes #27742).
Discussion
----------
[Process] Add feature "wait until callback" to process class
I often see code like the following:
```php
$process->start();
// wait for the process to be ready
sleep(3);
```
Many times in tests, sometimes in other places. There is a problem with this kind of code because if for any reason the process starts slower than the usual... Your code may fail after that. Also if it's faster, you're losing time for waiting.
So here is my proposal:
```php
$process->start();
$process->waitUntil(function($type, $output) {
// check the output and return true to stop waiting when you got what you wait
});
```
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | none
| License | MIT
| Doc PR | Waiting for feedbacks
Commits
-------
27eaf83b63
[Process] Add feature \"wait until callback\" to process class
This commit is contained in:
commit
4a4fda5679
@ -7,6 +7,8 @@ CHANGELOG
|
||||
* added the `Process::fromShellCommandline()` to run commands in a shell wrapper
|
||||
* deprecated passing a command as string when creating a `Process` instance
|
||||
* deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods
|
||||
* added the `Process::waitUntil()` method to wait for the process only for a
|
||||
specific output, then continue the normal execution of your application
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
@ -407,7 +407,7 @@ class Process implements \IteratorAggregate
|
||||
if (null !== $callback) {
|
||||
if (!$this->processPipes->haveReadSupport()) {
|
||||
$this->stop(0);
|
||||
throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait');
|
||||
throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
|
||||
}
|
||||
$this->callback = $this->buildCallback($callback);
|
||||
}
|
||||
@ -429,6 +429,45 @@ class Process implements \IteratorAggregate
|
||||
return $this->exitcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until the callback returns true.
|
||||
*
|
||||
* The callback receives the type of output (out or err) and some bytes
|
||||
* from the output in real-time while writing the standard input to the process.
|
||||
* It allows to have feedback from the independent process during execution.
|
||||
*
|
||||
* @param callable $callback
|
||||
*
|
||||
* @throws RuntimeException When process timed out
|
||||
* @throws LogicException When process is not yet started
|
||||
*/
|
||||
public function waitUntil(callable $callback)
|
||||
{
|
||||
$this->requireProcessIsStarted(__FUNCTION__);
|
||||
$this->updateStatus(false);
|
||||
|
||||
if (!$this->processPipes->haveReadSupport()) {
|
||||
$this->stop(0);
|
||||
throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
|
||||
}
|
||||
$callback = $this->buildCallback($callback);
|
||||
|
||||
$wait = true;
|
||||
do {
|
||||
$this->checkTimeout();
|
||||
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
|
||||
$output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
|
||||
|
||||
foreach ($output as $type => $data) {
|
||||
if (3 !== $type) {
|
||||
$wait = !$callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
|
||||
} elseif (!isset($this->fallbackStatus['signaled'])) {
|
||||
$this->fallbackStatus['exitcode'] = (int) $data;
|
||||
}
|
||||
}
|
||||
} while ($wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Pid (process identifier), if applicable.
|
||||
*
|
||||
@ -1264,7 +1303,7 @@ class Process implements \IteratorAggregate
|
||||
if ($this->outputDisabled) {
|
||||
return function ($type, $data) use ($callback) {
|
||||
if (null !== $callback) {
|
||||
\call_user_func($callback, $type, $data);
|
||||
return \call_user_func($callback, $type, $data);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1279,7 +1318,7 @@ class Process implements \IteratorAggregate
|
||||
}
|
||||
|
||||
if (null !== $callback) {
|
||||
\call_user_func($callback, $type, $data);
|
||||
return \call_user_func($callback, $type, $data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
$outputs = array(
|
||||
'First iteration output',
|
||||
'Second iteration output',
|
||||
'One more iteration output',
|
||||
'This took more time',
|
||||
'This one was sooooo slow',
|
||||
);
|
||||
|
||||
$iterationTime = 10000;
|
||||
|
||||
foreach ($outputs as $output) {
|
||||
usleep($iterationTime);
|
||||
$iterationTime *= 10;
|
||||
echo $output."\n";
|
||||
}
|
@ -133,6 +133,33 @@ class ProcessTest extends TestCase
|
||||
$this->assertLessThan(15, microtime(true) - $start);
|
||||
}
|
||||
|
||||
public function testWaitUntilSpecificOutput()
|
||||
{
|
||||
$p = $this->getProcess(array(self::$phpBin, __DIR__.'/KillableProcessWithOutput.php'));
|
||||
$p->start();
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
$completeOutput = '';
|
||||
$p->waitUntil(function ($type, $output) use (&$completeOutput) {
|
||||
$completeOutput .= $output;
|
||||
if (false !== strpos($output, 'One more')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
$p->stop();
|
||||
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
// Windows is slower
|
||||
$this->assertLessThan(15, microtime(true) - $start);
|
||||
} else {
|
||||
$this->assertLessThan(2, microtime(true) - $start);
|
||||
}
|
||||
$this->assertEquals("First iteration output\nSecond iteration output\nOne more iteration output\n", $completeOutput);
|
||||
}
|
||||
|
||||
public function testAllOutputIsActuallyReadOnTermination()
|
||||
{
|
||||
// this code will result in a maximum of 2 reads of 8192 bytes by calling
|
||||
|
Reference in New Issue
Block a user