Skip to content

Commit 00c81a7

Browse files
feature symfony#57915 [Messenger] Allow setting retry delay by RecoverableExceptionInterface (valtzu)
This PR was merged into the 7.2 branch. Discussion ---------- [Messenger] Allow setting retry delay by RecoverableExceptionInterface | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix symfony#57756 | License | MIT Allow overriding retry delay from the retry strategy by providing it in the exception. Example use case is retrying http request based on `Retry-After` header. Commits ------- 68a096c Allow setting retry delay by RecoverableExceptionInterface
2 parents acc8dda + 68a096c commit 00c81a7

File tree

6 files changed

+55
-1
lines changed

6 files changed

+55
-1
lines changed

UPGRADE-7.2.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ FrameworkBundle
2929

3030
* [BC BREAK] The `secrets:decrypt-to-local` command terminates with a non-zero exit code when a secret could not be read
3131

32+
Messenger
33+
---------
34+
35+
* Add `getRetryDelay()` method to `RecoverableExceptionInterface`
36+
3237
Security
3338
--------
3439

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* `WrappedExceptionsInterface` now extends PHP's `Throwable` interface
88
* Add `#[AsMessage]` attribute with `$transport` parameter for message routing
99
* Add `--format` option to the `messenger:stats` command
10+
* Add `getRetryDelay()` method to `RecoverableExceptionInterface`
1011

1112
7.1
1213
---

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ public function onMessageFailed(WorkerMessageFailedEvent $event): void
6363

6464
++$retryCount;
6565

66-
$delay = $retryStrategy->getWaitingTime($envelope, $throwable);
66+
$delay = null;
67+
if ($throwable instanceof RecoverableExceptionInterface && method_exists($throwable, 'getRetryDelay')) {
68+
$delay = $throwable->getRetryDelay();
69+
}
70+
71+
$delay ??= $retryStrategy->getWaitingTime($envelope, $throwable);
6772

6873
$this->logger?->warning('Error thrown while handling message {class}. Sending for retry #{retryCount} using {delay} ms delay. Error: "{error}"', $context + ['retryCount' => $retryCount, 'delay' => $delay, 'error' => $throwable->getMessage(), 'exception' => $throwable]);
6974

src/Symfony/Component/Messenger/Exception/RecoverableExceptionInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
* and the message should be retried, a handler can throw such an exception.
1919
*
2020
* @author Jérémy Derussé <[email protected]>
21+
*
22+
* @method int|null getRetryDelay() The time to wait in milliseconds
2123
*/
2224
interface RecoverableExceptionInterface extends \Throwable
2325
{

src/Symfony/Component/Messenger/Exception/RecoverableMessageHandlingException.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,13 @@
1818
*/
1919
class RecoverableMessageHandlingException extends RuntimeException implements RecoverableExceptionInterface
2020
{
21+
public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, private readonly ?int $retryDelay = null)
22+
{
23+
parent::__construct($message, $code, $previous);
24+
}
25+
26+
public function getRetryDelay(): ?int
27+
{
28+
return $this->retryDelay;
29+
}
2130
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,38 @@ public function testRecoverableStrategyCausesRetry()
7676
$listener->onMessageFailed($event);
7777
}
7878

79+
public function testRecoverableExceptionRetryDelayOverridesStrategy()
80+
{
81+
$sender = $this->createMock(SenderInterface::class);
82+
$sender->expects($this->once())->method('send')->willReturnCallback(function (Envelope $envelope) {
83+
$delayStamp = $envelope->last(DelayStamp::class);
84+
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
85+
86+
$this->assertInstanceOf(DelayStamp::class, $delayStamp);
87+
$this->assertSame(1234, $delayStamp->getDelay());
88+
89+
$this->assertInstanceOf(RedeliveryStamp::class, $redeliveryStamp);
90+
$this->assertSame(1, $redeliveryStamp->getRetryCount());
91+
92+
return $envelope;
93+
});
94+
$senderLocator = new Container();
95+
$senderLocator->set('my_receiver', $sender);
96+
$retryStrategy = $this->createMock(RetryStrategyInterface::class);
97+
$retryStrategy->expects($this->never())->method('isRetryable');
98+
$retryStrategy->expects($this->never())->method('getWaitingTime');
99+
$retryStrategyLocator = new Container();
100+
$retryStrategyLocator->set('my_receiver', $retryStrategy);
101+
102+
$listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator);
103+
104+
$exception = new RecoverableMessageHandlingException('retry', retryDelay: 1234);
105+
$envelope = new Envelope(new \stdClass());
106+
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
107+
108+
$listener->onMessageFailed($event);
109+
}
110+
79111
public function testEnvelopeIsSentToTransportOnRetry()
80112
{
81113
$exception = new \Exception('no!');

0 commit comments

Comments
 (0)