8000 [Messenger] Add support for RecoverableException · symfony/symfony@e7c3167 · GitHub
[go: up one dir, main page]

Skip to content

Commit e7c3167

Browse files
jderussefabpot
authored andcommitted
[Messenger] Add support for RecoverableException
1 parent 39aab26 commit e7c3167

File tree

5 files changed

+94
-1
lines changed

5 files changed

+94
-1
lines changed

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ CHANGELOG
88
* Moved Doctrine transport to package `symfony/doctrine-messenger`. All classes in `Symfony\Component\Messenger\Transport\Doctrine` have been moved to `Symfony\Component\Messenger\Bridge\Doctrine\Transport`
99
* Moved RedisExt transport to package `symfony/redis-messenger`. All classes in `Symfony\Component\Messenger\Transport\RedisExt` have been moved to `Symfony\Component\Messenger\Bridge\Redis\Transport`
1010
* Added support for passing a `\Throwable` argument to `RetryStrategyInterface` methods. This allows to define strategies based on the reason of the handling failure.
11-
* Added `StopWorkerOnFailureLimitListener` to stop the worker after a specified amount of failed messages is reached.
11+
* Added `StopWorkerOnFailureLimitListener` to stop the worker after a specified amount of failed messages is reached.
12+
* Added `RecoverableExceptionInterface` interface to force retry.
1213

1314
5.0.0
1415
-----

src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Messenger\Envelope;
1717
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
1818
use Symfony\Component\Messenger\Exception\HandlerFailedException;
19+
use Symfony\Component\Messenger\Exception\RecoverableExceptionInterface;
1920
use Symfony\Component\Messenger\Exception\RuntimeException;
2021
use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
2122
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
@@ -87,10 +88,19 @@ public static function getSubscribedEvents()
8788

8889
private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool
8990
{
91+
if ($e instanceof RecoverableExceptionInterface) {
92+
return true;
93+
}
94+
95+
// if one or more nested Exceptions is an instance of RecoverableExceptionInterface we should retry
9096
// if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry
9197
if ($e instanceof HandlerFailedException) {
9298
$shouldNotRetry = true;
9399
foreach ($e->getNestedExceptions() as $nestedException) {
100+
if ($nestedException instanceof RecoverableExceptionInterface) {
101+
return true;
102+
}
103+
94104
if (!$nestedException instanceof UnrecoverableExceptionInterface) {
95105
$shouldNotRetry = false;
96106
break;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Exception;
13+
14+
/**
15+
* Marker interface for exceptions to indicate that handling a message should have worked.
16+
*
17+
* If something goes wrong while handling a message that's received from a transport
18+
* and the message should must be retried, a handler can throw such an exception.
19+
*
20+
* @author Jérémy Derussé <jeremy@derusse.com>
21+
*/
22+
interface RecoverableExceptionInterface extends \Throwable
23+
{
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Exception;
13+
14+
/**
15+
* A concrete implementation of RecoverableExceptionInterface that can be used directly.
16+
*
17+
* @author Frederic Bouchery <frederic@bouchery.fr>
18+
*/
19+
class RecoverableMessageHandlingException extends RuntimeException implements RecoverableExceptionInterface
20+
{
21+
}

src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Messenger\Envelope;
1717
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
1818
use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener;
19+
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;
1920
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
2021
use Symfony\Component\Messenger\Stamp\DelayStamp;
2122
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
@@ -40,6 +41,42 @@ public function testNoRetryStrategyCausesNoRetry()
4041
$listener->onMessageFailed($event);
4142
}
4243

44+
public function testRecoverableStrategyCausesRetry()
45+
{
46+
$sender = $this->createMock(SenderInterface::class);
47+
$sender->expects($this->once())->method('send')->willReturnCallback(fu F438 nction (Envelope $envelope) {
48+
/** @var DelayStamp $delayStamp */
49+
$delayStamp = $envelope->last(DelayStamp::class);
50+
/** @var RedeliveryStamp $redeliveryStamp */
51+
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
52+
53+
$this->assertInstanceOf(DelayStamp::class, $delayStamp);
54+
$this->assertSame(1000, $delayStamp->getDelay());
55+
56+
$this->assertInstanceOf(RedeliveryStamp::class, $redeliveryStamp);
57+
$this->assertSame(1, $redeliveryStamp->getRetryCount());
58+
59+
return $envelope;
60+
});
61+
$senderLocator = $this->createMock(ContainerInterface::class);
62+
$senderLocator->expects($this->once())->method('has')->willReturn(true);
63+
$senderLocator->expects($this->once())->method('get')->willReturn($sender);
64+
$retryStategy = $this->createMock(RetryStrategyInterface::class);
65+
$retryStategy->expects($this->never())->method('isRetryable');
66+
$retryStategy->expects($this->once())->method('getWaitingTime')->willReturn(1000);
67+
$retryStrategyLocator = $this->createMock(ContainerInterface::class);
68+
$retryStrategyLocator->expects($this->once())->method('has')->willReturn(true);
69+
$retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStategy);
70+
71+
$listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator);
72+
73+
$exception = new RecoverableMessageHandlingException('retry');
74+
$envelope = new Envelope(new \stdClass());
75+
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
76+
77+
$listener->onMessageFailed($event);
78+
}
79+
4380
public function testEnvelopeIsSentToTransportOnRetry()
4481
{
4582
$exception = new \Exception('no!');

0 commit comments

Comments
 (0)
0