2 namespace Robo\Task\Base;
4 use Robo\Contract\CommandInterface;
5 use Robo\Contract\PrintedInterface;
7 use Robo\Task\BaseTask;
8 use Symfony\Component\Process\Exception\ProcessTimedOutException;
9 use Symfony\Component\Process\Process;
12 * Class ParallelExecTask
16 * $this->taskParallelExec()
17 * ->process('php ~/demos/script.php hey')
18 * ->process('php ~/demos/script.php hoy')
19 * ->process('php ~/demos/script.php gou')
24 class ParallelExec extends BaseTask implements CommandInterface, PrintedInterface
26 use \Robo\Common\CommandReceiver;
31 protected $processes = [];
36 protected $timeout = null;
41 protected $idleTimeout = null;
46 protected $waitInterval = 0;
51 protected $isPrinted = false;
56 public function getPrinted()
58 return $this->isPrinted;
62 * @param bool $isPrinted
66 public function printed($isPrinted = true)
68 $this->isPrinted = $isPrinted;
73 * @param string|\Robo\Contract\CommandInterface $command
77 public function process($command)
79 // TODO: Symfony 4 requires that we supply the working directory.
80 $this->processes[] = new Process($this->receiveCommand($command), getcwd());
85 * Stops process if it runs longer then `$timeout` (seconds).
91 public function timeout($timeout)
93 $this->timeout = $timeout;
98 * Stops process if it does not output for time longer then `$timeout` (seconds).
100 * @param int $idleTimeout
104 public function idleTimeout($idleTimeout)
106 $this->idleTimeout = $idleTimeout;
111 * Parallel processing will wait `$waitInterval` seconds after launching each process and before
114 * @param int $waitInterval
118 public function waitInterval($waitInterval)
120 $this->waitInterval = $waitInterval;
127 public function getCommand()
129 return implode(' && ', $this->processes);
135 public function progressIndicatorSteps()
137 return count($this->processes);
143 public function run()
145 $this->startProgressIndicator();
147 $queue = $this->processes;
150 if (($nextTime <= time()) && !empty($queue)) {
151 $process = array_shift($queue);
152 $process->setIdleTimeout($this->idleTimeout);
153 $process->setTimeout($this->timeout);
155 $this->printTaskInfo($process->getCommandLine());
156 $running[] = $process;
157 $nextTime = time() + $this->waitInterval;
159 foreach ($running as $k => $process) {
161 $process->checkTimeout();
162 } catch (ProcessTimedOutException $e) {
163 $this->printTaskWarning("Process timed out for {command}", ['command' => $process->getCommandLine(), '_style' => ['command' => 'fg=white;bg=magenta']]);
165 if (!$process->isRunning()) {
166 $this->advanceProgressIndicator();
167 if ($this->isPrinted) {
168 $this->printTaskInfo("Output for {command}:\n\n{output}", ['command' => $process->getCommandLine(), 'output' => $process->getOutput(), '_style' => ['command' => 'fg=white;bg=magenta']]);
169 $errorOutput = $process->getErrorOutput();
171 $this->printTaskError(rtrim($errorOutput));
177 if (empty($running) && empty($queue)) {
182 $this->stopProgressIndicator();
186 foreach ($this->processes as $p) {
187 if ($p->getExitCode() === 0) {
190 $errorMessage .= "'" . $p->getCommandLine() . "' exited with code ". $p->getExitCode()." \n";
191 $exitCode = max($exitCode, $p->getExitCode());
193 if (!$errorMessage) {
194 $this->printTaskSuccess('{process-count} processes finished running', ['process-count' => count($this->processes)]);
197 return new Result($this, $exitCode, $errorMessage, ['time' => $this->getExecutionTime()]);