Skip to content

Commit c6bbff0

Browse files
committed
bug #59513 [Messenger ] Extract retry delay from nested RecoverableExceptionInterface (AydinHassan)
This PR was merged into the 7.2 branch. Discussion ---------- [Messenger ] Extract retry delay from nested `RecoverableExceptionInterface` | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | n/a | License | MIT Hey, In #57915 the ability to add a retry delay to `RecoverableExceptionInterface` was implemented. However, I noticed while debugging that it in my case the custom delay was not being applied because `RecoverableExceptionInterface` is wrapped in a `HandlerFailedException` in: https://github.com/symfony/symfony/blob/7a16efefdee2d282cd6ccfd6ecb0b82ff5a06936/src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php#L124 For detecting whether we should retry we already unwrap `HandlerFailedException` so I implemented a similar method to fetch the delay, simply getting it from the first `RecoverableExceptionInterface`. Maybe we need something more advanced, so your feedback there would be appreciated! Commits ------- 4a02222 [Messenger ] Extract retry delay from nested `RecoverableExceptionInterface`
2 parents 90103f1 + 4a02222 commit c6bbff0

File tree

2 files changed

+82
-6
lines changed

2 files changed

+82
-6
lines changed

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

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

6464
++$retryCount;
6565

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

7368
$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]);
7469

@@ -148,6 +143,30 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt
148143
return $retryStrategy->isRetryable($envelope, $e);
149144
}
150145

146+
private function getWaitingTime(Envelope $envelope, \Throwable $throwable, RetryStrategyInterface $retryStrategy): int
147+
{
148+
$delay = null;
149+
if ($throwable instanceof RecoverableExceptionInterface && method_exists($throwable, 'getRetryDelay')) {
150+
$delay = $throwable->getRetryDelay();
151+
}
152+
153+
if ($throwable instanceof HandlerFailedException) {
154+
foreach ($throwable->getWrappedExceptions() as $nestedException) {
155+
if (!$nestedException instanceof RecoverableExceptionInterface
156+
|| !method_exists($nestedException, 'getRetryDelay')
157+
|| 0 > $retryDelay = $nestedException->getRetryDelay() ?? -1
158+
) {
159+
continue;
160+
}
161+
if ($retryDelay < ($delay ?? \PHP_INT_MAX)) {
162+
$delay = $retryDelay;
163+
}
164+
}
165+
}
166+
167+
return $delay ?? $retryStrategy->getWaitingTime($envelope, $throwable);
168+
}
169+
151170
private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
152171
{
153172
if ($this->retryStrategyLocator->has($alias)) {

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
1919
use Symfony\Component\Messenger\Event\WorkerMessageRetriedEvent;
2020
use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener;
21+
use Symfony\Component\Messenger\Exception\HandlerFailedException;
2122
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;
2223
use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
2324
use Symfony\Component\Messenger\Stamp\DelayStamp;
@@ -108,6 +109,62 @@ public function testRecoverableExceptionRetryDelayOverridesStrategy()
108109
$listener->onMessageFailed($event);
109110
}
110111

112+
/**
113+
* @dataProvider provideRetryDelays
114+
*/
115+
public function testWrappedRecoverableExceptionRetryDelayOverridesStrategy(array $retries, int $expectedDelay)
116+
{
117+
$sender = $this->createMock(SenderInterface::class);
118+
$sender->expects($this->once())->method('send')->willReturnCallback(function (Envelope $envelope) use ($expectedDelay) {
119+
$delayStamp = $envelope->last(DelayStamp::class);
120+
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
121+
122+
$this->assertInstanceOf(DelayStamp::class, $delayStamp);
123+
$this->assertSame($expectedDelay, $delayStamp->getDelay());
124+
125+
$this->assertInstanceOf(RedeliveryStamp::class, $redeliveryStamp);
126+
$this->assertSame(1, $redeliveryStamp->getRetryCount());
127+
128+
return $envelope;
129+
});
130+
$senderLocator = new Container();
131+
$senderLocator->set('my_receiver', $sender);
132+
$retryStrategy = $this->createMock(RetryStrategyInterface::class);
133+
$retryStrategy->expects($this->never())->method('isRetryable');
134+
$retryStrategy->expects($this->never())->method('getWaitingTime');
135+
$retryStrategyLocator = new Container();
136+
$retryStrategyLocator->set('my_receiver', $retryStrategy);
137+
138+
$listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator);
139+
140+
$envelope = new Envelope(new \stdClass());
141+
$exception = new HandlerFailedException(
142+
$envelope,
143+
array_map(fn (int $retry) => new RecoverableMessageHandlingException('retry', retryDelay: $retry), $retries)
144+
);
145+
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
146+
147+
$listener->onMessageFailed($event);
148+
}
149+
150+
public static function provideRetryDelays(): iterable
151+
{
152+
yield 'one_exception' => [
153+
[1235],
154+
1235,
155+
];
156+
157+
yield 'multiple_exceptions' => [
158+
[1235, 2000, 1000],
159+
1000,
160+
];
161+
162+
yield 'zero_delay' => [
163+
[0, 2000, 1000],
164+
0,
165+
];
166+
}
167+
111168
public function testEnvelopeIsSentToTransportOnRetry()
112169
{
113170
$exception = new \Exception('no!');

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy