Skip to content

Commit c70f35c

Browse files
authored
Merge pull request #3 from BluePsyduck/issue-2_timeout
#1: Catch timeout exceptions
2 parents 44d5438 + fa8b069 commit c70f35c

File tree

5 files changed

+121
-8
lines changed

5 files changed

+121
-8
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## 1.2.0 - 2018-12-03
4+
5+
### Added
6+
7+
- New callback `processTimeoutCallback`.
8+
9+
### Fixed
10+
11+
- Process manager failed to continue executing processes when one of them timed out.
12+
313
## 1.1.0 - 2018-10-07
414

515
### Added

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ $processManager = new ProcessManager($numberOfParallelProcesses, $pollInterval,
2727
// Add some processes
2828
// Processes get executed automatically once they are added to the manager.
2929
// If the limit of parallel processes is reached, they are placed in a queue and wait for a process to finish.
30-
$processManager->addProcess(new Process('ls -l'));
31-
$processManager->addProcess(new Process('ls -l'));
30+
$processManager->addProcess(Process::fromShellCommandline('ls -l'));
31+
$processManager->addProcess(Process::fromShellCommandline('ls -l'));
3232

3333
// Wait for all processes to finish
3434
$processManager->waitForAllProcesses();
@@ -42,6 +42,8 @@ The process manager allows for some callbacks to be specified, which get called
4242

4343
* **processStartCallback:** Triggered before a process is started.
4444
* **processFinishCallback:** Triggered when a process has finished.
45+
* **processTimeoutCallback:** Triggered when a process timed out. Note that the _processFinishCallback_ will be
46+
triggered afterwards as well.
4547

4648
Each callback gets the process instance which triggered the event passed as only parameter. Here is an example of
4749
setting a `processStartCallback`:
@@ -57,6 +59,6 @@ $processManager->setProcessStartCallback(function (Process $process): void {
5759
echo 'Starting process: ' . $process->getCommandLine();
5860
});
5961

60-
$processManager->addProcess(new Process('ls -l'));
62+
$processManager->addProcess(Process::fromShellCommandline('ls -l'));
6163
$processManager->waitForAllProcesses();
6264
```

src/ProcessManager.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace BluePsyduck\SymfonyProcessManager;
66

7+
use Symfony\Component\Process\Exception\ProcessTimedOutException;
78
use Symfony\Component\Process\Process;
89

910
/**
@@ -56,6 +57,12 @@ class ProcessManager implements ProcessManagerInterface
5657
*/
5758
protected $processFinishCallback;
5859

60+
/**
61+
* The callback for when a process timed out.
62+
* @var callable|null
63+
*/
64+
protected $processTimeoutCallback;
65+
5966
/**
6067
* ProcessManager constructor.
6168
* @param int $numberOfParallelProcesses The number of processes to run in parallel.
@@ -128,6 +135,17 @@ public function setProcessFinishCallback(?callable $processFinishCallback)
128135
return $this;
129136
}
130137

138+
/**
139+
* Sets the callback for when a process timed out.
140+
* @param callable|null $processTimeoutCallback
141+
* @return $this
142+
*/
143+
public function setProcessTimeoutCallback(?callable $processTimeoutCallback)
144+
{
145+
$this->processTimeoutCallback = $processTimeoutCallback;
146+
return $this;
147+
}
148+
131149
/**
132150
* Invokes the callback if it is an callable.
133151
* @param callable|null $callback
@@ -205,7 +223,7 @@ protected function checkRunningProcesses(): void
205223
*/
206224
protected function checkRunningProcess(?int $pid, Process $process): void
207225
{
208-
$process->checkTimeout();
226+
$this->checkProcessTimeout($process);
209227
if (!$process->isRunning()) {
210228
$this->invokeCallback($this->processFinishCallback, $process);
211229

@@ -216,6 +234,19 @@ protected function checkRunningProcess(?int $pid, Process $process): void
216234
}
217235
}
218236

237+
/**
238+
* Checks whether the process already timed out.
239+
* @param Process $process
240+
*/
241+
protected function checkProcessTimeout(Process $process): void
242+
{
243+
try {
244+
$process->checkTimeout();
245+
} catch (ProcessTimedOutException $e) {
246+
$this->invokeCallback($this->processTimeoutCallback, $process);
247+
}
248+
}
249+
219250
/**
220251
* Waits for all processes to be finished.
221252
* @return $this

src/ProcessManagerInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace BluePsyduck\SymfonyProcessManager;
46

57
use Symfony\Component\Process\Process;

test/src/ProcessManagerTest.php

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPUnit\Framework\MockObject\MockObject;
1010
use PHPUnit\Framework\TestCase;
1111
use ReflectionException;
12+
use Symfony\Component\Process\Exception\ProcessTimedOutException;
1213
use Symfony\Component\Process\Process;
1314

1415
/**
@@ -123,6 +124,21 @@ public function testSetProcessFinishCallback(): void
123124
$this->assertSame($callback, $this->extractProperty($manager, 'processFinishCallback'));
124125
}
125126

127+
/**
128+
* Tests the setProcessTimeoutCallback method.
129+
* @covers ::setProcessTimeoutCallback
130+
* @throws ReflectionException
131+
*/
132+
public function testSetProcessTimeoutCallback(): void
133+
{
134+
$callback = 'strval';
135+
136+
$manager = new ProcessManager();
137+
$result = $manager->setProcessTimeoutCallback($callback);
138+
$this->assertSame($manager, $result);
139+
$this->assertSame($callback, $this->extractProperty($manager, 'processTimeoutCallback'));
140+
}
141+
126142
/**
127143
* Provides the data for the invokeCallback test.
128144
* @return array
@@ -376,11 +392,9 @@ public function testCheckRunningProcess(
376392
): void {
377393
/* @var Process|MockObject $process */
378394
$process = $this->getMockBuilder(Process::class)
379-
->setMethods(['checkTimeout', 'isRunning'])
395+
->setMethods(['isRunning'])
380396
->disableOriginalConstructor()
381397
->getMock();
382-
$process->expects($this->once())
383-
->method('checkTimeout');
384398
$process->expects($this->once())
385399
->method('isRunning')
386400
->willReturn($resultIsRunning);
@@ -392,9 +406,13 @@ public function testCheckRunningProcess(
392406

393407
/* @var ProcessManager|MockObject $manager */
394408
$manager = $this->getMockBuilder(ProcessManager::class)
395-
->setMethods(['invokeCallback', 'executeNextPendingProcess'])
409+
->setMethods(['checkProcessTimeout', 'invokeCallback', 'executeNextPendingProcess'])
396410
->disableOriginalConstructor()
397411
->getMock();
412+
$manager->expects($this->once())
413+
->method('checkProcessTimeout')
414+
->with($process)
415+
->willReturn(false);
398416
$manager->expects($expectFinish ? $this->once() : $this->never())
399417
->method('invokeCallback')
400418
->with('strval', $process);
@@ -407,6 +425,56 @@ public function testCheckRunningProcess(
407425
$this->assertEquals($expectedRunningProcesses, $this->extractProperty($manager, 'runningProcesses'));
408426
}
409427

428+
/**
429+
* Provides the data for the checkProcessTimeout test.
430+
* @return array
431+
*/
432+
public function provideCheckProcessTimeout(): array
433+
{
434+
return [
435+
[false, false],
436+
[true, true],
437+
];
438+
}
439+
440+
/**
441+
* Tests the checkProcessTimeout method.
442+
* @param bool $throwException
443+
* @param bool $expectInvoke
444+
* @throws ReflectionException
445+
* @covers ::checkProcessTimeout
446+
* @dataProvider provideCheckProcessTimeout
447+
*/
448+
public function testCheckProcessTimeout(bool $throwException, bool $expectInvoke): void
449+
{
450+
/* @var Process|MockObject $process */
451+
$process = $this->getMockBuilder(Process::class)
452+
->setMethods(['checkTimeout'])
453+
->disableOriginalConstructor()
454+
->getMock();
455+
456+
if ($throwException) {
457+
$process->expects($this->once())
458+
->method('checkTimeout')
459+
->willThrowException($this->createMock(ProcessTimedOutException::class));
460+
} else {
461+
$process->expects($this->once())
462+
->method('checkTimeout');
463+
}
464+
465+
/* @var ProcessManager|MockObject $manager */
466+
$manager = $this->getMockBuilder(ProcessManager::class)
467+
->setMethods(['invokeCallback'])
468+
->disableOriginalConstructor()
469+
->getMock();
470+
$manager->expects($expectInvoke ? $this->once() : $this->never())
471+
->method('invokeCallback')
472+
->with('strval', $process);
473+
$manager->setProcessTimeoutCallback('strval');
474+
475+
$this->invokeMethod($manager, 'checkProcessTimeout', $process);
476+
}
477+
410478
/**
411479
* Tests the waitForAllProcesses method.
412480
* @covers ::waitForAllProcesses

0 commit comments

Comments
 (0)