Skip to content

Commit 5a6c912

Browse files
wachterjohanneschirimoya
authored andcommitted
Implemented retry-exectution for specific handler (#41)
* added additional field to execution * added retry to separate-process executor * added upgrade note * updated dependencies
1 parent cfc46bb commit 5a6c912

File tree

8 files changed

+376
-19
lines changed

8 files changed

+376
-19
lines changed

UPGRADE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
UPGRADE
22
=======
33

4+
- [1.2.0](#1.2.0)
45
- [1.1.0](#1.1.0)
56
- [0.4.0](#0.4.0)
67

8+
### 1.2.0
9+
10+
In the database table `ta_task_executions` a new field was introduced. Run following
11+
command to update the table.
12+
13+
```bash
14+
bin/console doctrine:schema:update
15+
```
16+
717
### 1.1.0
818

919
In the database table `ta_tasks` a new field was introduced. Run following

src/Command/ExecuteCommand.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Console\Input\InputInterface;
1717
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1818
use Symfony\Component\Console\Output\OutputInterface;
19+
use Task\Executor\FailedException;
1920
use Task\Handler\TaskHandlerFactoryInterface;
2021
use Task\Storage\TaskExecutionRepositoryInterface;
2122

@@ -63,15 +64,20 @@ protected function configure()
6364
*/
6465
protected function execute(InputInterface $input, OutputInterface $output)
6566
{
66-
$errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
67+
$errorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
6768

6869
$execution = $this->executionRepository->findByUuid($input->getArgument('uuid'));
6970
$handler = $this->handlerFactory->create($execution->getHandlerClass());
7071

7172
try {
7273
$result = $handler->handle($execution->getWorkload());
73-
} catch (\Exception $e) {
74-
$errOutput->writeln($e->__toString());
74+
} catch (\Exception $exception) {
75+
if ($exception instanceof FailedException) {
76+
$errorOutput->writeln(FailedException::class);
77+
$exception = $exception->getPrevious();
78+
}
79+
80+
$errorOutput->writeln($exception->__toString());
7581

7682
// Process exit-code: 0 = OK, >1 = FAIL
7783
return 1;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Task\TaskBundle\Executor;
4+
5+
use Symfony\Component\Process\Process;
6+
use Symfony\Component\Process\ProcessBuilder;
7+
8+
/**
9+
* Factory for execution-process.
10+
*/
11+
class ExecutionProcessFactory
12+
{
13+
/**
14+
* @var string
15+
*/
16+
private $consolePath;
17+
18+
/**
19+
* @var string
20+
*/
21+
private $environment;
22+
23+
/**
24+
* @param string $consolePath
25+
* @param string $environment
26+
*/
27+
public function __construct($consolePath, $environment)
28+
{
29+
$this->consolePath = $consolePath;
30+
$this->environment = $environment;
31+
}
32+
33+
/**
34+
* Create process for given execution-uuid.
35+
*
36+
* @param string $uuid
37+
*
38+
* @return Process
39+
*/
40+
public function create($uuid)
41+
{
42+
return $process = ProcessBuilder::create(
43+
[$this->consolePath, 'task:execute', $uuid, '-e ' . $this->environment]
44+
)->getProcess();
45+
}
46+
}

src/Executor/SeparateProcessExecutor.php

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,50 +11,132 @@
1111

1212
namespace Task\TaskBundle\Executor;
1313

14-
use Symfony\Component\Process\ProcessBuilder;
1514
use Task\Execution\TaskExecutionInterface;
1615
use Task\Executor\ExecutorInterface;
16+
use Task\Executor\FailedException;
17+
use Task\Executor\RetryTaskHandlerInterface;
18+
use Task\Handler\TaskHandlerFactoryInterface;
19+
use Task\Storage\TaskExecutionRepositoryInterface;
1720

1821
/**
1922
* Uses a separate process to start the executions via console-command.
2023
*/
2124
class SeparateProcessExecutor implements ExecutorInterface
2225
{
2326
/**
24-
* @var string
27+
* @var TaskHandlerFactoryInterface
2528
*/
26-
private $consolePath;
29+
private $handlerFactory;
2730

2831
/**
29-
* @var string
32+
* @var TaskExecutionRepositoryInterface
3033
*/
31-
private $environment;
34+
private $executionRepository;
3235

3336
/**
34-
* @param string $consolePath
35-
* @param string $environment
37+
* @var ExecutionProcessFactory
3638
*/
37-
public function __construct($consolePath, $environment)
38-
{
39-
$this->consolePath = $consolePath;
40-
$this->environment = $environment;
39+
private $processFactory;
40+
41+
/**
42+
* @param TaskHandlerFactoryInterface $handlerFactory
43+
* @param TaskExecutionRepositoryInterface $executionRepository
44+
* @param ExecutionProcessFactory $processFactory
45+
*/
46+
public function __construct(
47+
TaskHandlerFactoryInterface $handlerFactory,
48+
TaskExecutionRepositoryInterface $executionRepository,
49+
ExecutionProcessFactory $processFactory
50+
) {
51+
$this->handlerFactory = $handlerFactory;
52+
$this->executionRepository = $executionRepository;
53+
$this->processFactory = $processFactory;
4154
}
4255

4356
/**
4457
* {@inheritdoc}
4558
*/
4659
public function execute(TaskExecutionInterface $execution)
4760
{
48-
$process = ProcessBuilder::create(
49-
[$this->consolePath, 'task:execute', $execution->getUuid(), '-e ' . $this->environment]
50-
)->getProcess();
61+
$attempts = $this->getMaximumAttempts($execution->getHandlerClass());
62+
$lastException = null;
63+
64+
for ($attempt = 0; $attempt < $attempts; ++$attempt) {
65+
try {
66+
return $this->handle($execution);
67+
} catch (FailedException $exception) {
68+
throw $exception;
69+
} catch (SeparateProcessException $exception) {
70+
if ($execution->getAttempts() < $attempts) {
71+
$execution->incrementAttempts();
72+
$this->executionRepository->save($execution);
73+
}
74+
75+
$lastException = $exception;
76+
}
77+
}
78+
79+
// maximum attempts to pass executions are reached
80+
throw new FailedException($lastException);
81+
}
82+
83+
/**
84+
* Returns maximum attempts for specified handler.
85+
*
86+
* @param string $handlerClass
87+
*
88+
* @return int
89+
*/
90+
private function getMaximumAttempts($handlerClass)
91+
{
92+
$handler = $this->handlerFactory->create($handlerClass);
93+
if (!$handler instanceof RetryTaskHandlerInterface) {
94+
return 1;
95+
}
5196

97+
return $handler->getMaximumAttempts();
98+
}
99+
100+
/**
101+
* Handle execution by using console-command.
102+
*
103+
* @param TaskExecutionInterface $execution
104+
*
105+
* @return string
106+
*
107+
* @throws FailedException
108+
* @throws SeparateProcessException
109+
*/
110+
private function handle(TaskExecutionInterface $execution)
111+
{
112+
$process = $this->processFactory->create($execution->getUuid());
52113
$process->run();
53114

54115
if (!$process->isSuccessful()) {
55-
throw new SeparateProcessException($process->getErrorOutput());
116+
throw $this->createException($process->getErrorOutput());
56117
}
57118

58119
return $process->getOutput();
59120
}
121+
122+
/**
123+
* Create the correct exception.
124+
*
125+
* FailedException for failed executions.
126+
* SeparateProcessExceptions for any exception during execution.
127+
*
128+
* @param string $errorOutput
129+
*
130+
* @return FailedException|SeparateProcessException
131+
*/
132+
private function createException($errorOutput)
133+
{
134+
if (strpos($errorOutput, FailedException::class) !== 0) {
135+
return new SeparateProcessException($errorOutput);
136+
}
137+
138+
$errorOutput = trim(str_replace(FailedException::class, '', $errorOutput));
139+
140+
return new FailedException(new SeparateProcessException($errorOutput));
141+
}
60142
}

src/Resources/config/doctrine/TaskExecution.orm.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
<field name="exception" type="text" nullable="true"/>
2323
<field name="result" type="object" nullable="true"/>
2424
<field name="status" type="string" length="20"/>
25-
25+
<field name="attempts" type="integer"/>
26+
2627
<many-to-one target-entity="Task\TaskBundle\Entity\Task" field="task">
2728
<join-column name="task_id" referenced-column-name="uuid" on-delete="CASCADE"/>
2829
</many-to-one>

src/Resources/config/executor/separate.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
55
<services>
66
<service id="task.executor.separate" class="Task\TaskBundle\Executor\SeparateProcessExecutor">
7+
<argument type="service" id="task.handler.factory"/>
8+
<argument type="service" id="task.storage.task_execution"/>
9+
<argument type="service" id="task.executor.separate.process_factory"/>
10+
</service>
11+
12+
<service id="task.executor.separate.process_factory" class="Task\TaskBundle\Executor\ExecutionProcessFactory">
713
<argument type="string">%task.executor.console_path%</argument>
814
<argument type="string">%kernel.environment%%</argument>
915
</service>

src/Resources/config/task_event_listener.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<parameter key="task.events.finished" type="constant">Task\Event\Events::TASK_FINISHED</parameter>
1212
<parameter key="task.events.passed" type="constant">Task\Event\Events::TASK_PASSED</parameter>
1313
<parameter key="task.events.failed" type="constant">Task\Event\Events::TASK_FAILED</parameter>
14+
<parameter key="task.events.retried" type="constant">Task\Event\Events::TASK_RETRIED</parameter>
1415
</parameters>
1516

1617
</container>

0 commit comments

Comments
 (0)