feature #36185 [Messenger] Add a \Throwable argument in RetryStrategyInterface methods (Benjamin Dos Santos)
This PR was squashed before being merged into the 5.1-dev branch.
Discussion
----------
[Messenger] Add a \Throwable argument in RetryStrategyInterface methods
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | Fix #36182
| License | MIT
This allows to define new retry strategies based on the exceptions thrown during the last handling.
Commits
-------
5fa9d68e8b
[Messenger] Add a \Throwable argument in RetryStrategyInterface methods
This commit is contained in:
commit
fdd8ac5f25
@ -73,6 +73,8 @@ Messenger
|
|||||||
* Deprecated Doctrine transport. It has moved to a separate package. Run `composer require symfony/doctrine-messenger` to use the new classes.
|
* Deprecated Doctrine transport. It has moved to a separate package. Run `composer require symfony/doctrine-messenger` to use the new classes.
|
||||||
* Deprecated RedisExt transport. It has moved to a separate package. Run `composer require symfony/redis-messenger` to use the new classes.
|
* Deprecated RedisExt transport. It has moved to a separate package. Run `composer require symfony/redis-messenger` to use the new classes.
|
||||||
* Deprecated use of invalid options in Redis and AMQP connections.
|
* Deprecated use of invalid options in Redis and AMQP connections.
|
||||||
|
* Deprecated *not* declaring a `\Throwable` argument in `RetryStrategyInterface::isRetryable()`
|
||||||
|
* Deprecated *not* declaring a `\Throwable` argument in `RetryStrategyInterface::getWaitingTime()`
|
||||||
|
|
||||||
Notifier
|
Notifier
|
||||||
--------
|
--------
|
||||||
|
@ -65,6 +65,8 @@ Messenger
|
|||||||
* Removed Doctrine transport. Run `composer require symfony/doctrine-messenger` to keep the transport in your application.
|
* Removed Doctrine transport. Run `composer require symfony/doctrine-messenger` to keep the transport in your application.
|
||||||
* Removed RedisExt transport. Run `composer require symfony/redis-messenger` to keep the transport in your application.
|
* Removed RedisExt transport. Run `composer require symfony/redis-messenger` to keep the transport in your application.
|
||||||
* Use of invalid options in Redis and AMQP connections now throws an error.
|
* Use of invalid options in Redis and AMQP connections now throws an error.
|
||||||
|
* The signature of method `RetryStrategyInterface::isRetryable()` has been updated to `RetryStrategyInterface::isRetryable(Envelope $message, \Throwable $throwable = null)`.
|
||||||
|
* The signature of method `RetryStrategyInterface::getWaitingTime()` has been updated to `RetryStrategyInterface::getWaitingTime(Envelope $message, \Throwable $throwable = null)`.
|
||||||
|
|
||||||
PhpUnitBridge
|
PhpUnitBridge
|
||||||
-------------
|
-------------
|
||||||
|
@ -7,6 +7,7 @@ CHANGELOG
|
|||||||
* Moved AmqpExt transport to package `symfony/amqp-messenger`. All classes in `Symfony\Component\Messenger\Transport\AmqpExt` have been moved to `Symfony\Component\Messenger\Bridge\Amqp\Transport`
|
* Moved AmqpExt transport to package `symfony/amqp-messenger`. All classes in `Symfony\Component\Messenger\Transport\AmqpExt` have been moved to `Symfony\Component\Messenger\Bridge\Amqp\Transport`
|
||||||
* 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`
|
* 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`
|
||||||
* 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`
|
* 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`
|
||||||
|
* Added support for passing a `\Throwable` argument to `RetryStrategyInterface` methods. This allows to define strategies based on the reason of the handling failure.
|
||||||
|
|
||||||
5.0.0
|
5.0.0
|
||||||
-----
|
-----
|
||||||
|
@ -58,7 +58,9 @@ class SendFailedMessageForRetryListener implements EventSubscriberInterface
|
|||||||
$event->setForRetry();
|
$event->setForRetry();
|
||||||
|
|
||||||
++$retryCount;
|
++$retryCount;
|
||||||
$delay = $retryStrategy->getWaitingTime($envelope);
|
|
||||||
|
$delay = $retryStrategy->getWaitingTime($envelope, $throwable);
|
||||||
|
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
$this->logger->error('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]);
|
$this->logger->error('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]);
|
||||||
}
|
}
|
||||||
@ -103,7 +105,7 @@ class SendFailedMessageForRetryListener implements EventSubscriberInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $retryStrategy->isRetryable($envelope);
|
return $retryStrategy->isRetryable($envelope, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
|
private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
|
||||||
|
@ -63,14 +63,20 @@ class MultiplierRetryStrategy implements RetryStrategyInterface
|
|||||||
$this->maxDelayMilliseconds = $maxDelayMilliseconds;
|
$this->maxDelayMilliseconds = $maxDelayMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isRetryable(Envelope $message): bool
|
/**
|
||||||
|
* @param \Throwable|null $throwable The cause of the failed handling
|
||||||
|
*/
|
||||||
|
public function isRetryable(Envelope $message, \Throwable $throwable = null): bool
|
||||||
{
|
{
|
||||||
$retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
|
$retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
|
||||||
|
|
||||||
return $retries < $this->maxRetries;
|
return $retries < $this->maxRetries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWaitingTime(Envelope $message): int
|
/**
|
||||||
|
* @param \Throwable|null $throwable The cause of the failed handling
|
||||||
|
*/
|
||||||
|
public function getWaitingTime(Envelope $message, \Throwable $throwable = null): int
|
||||||
{
|
{
|
||||||
$retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
|
$retries = RedeliveryStamp::getRetryCountFromEnvelope($message);
|
||||||
|
|
||||||
|
@ -20,10 +20,15 @@ use Symfony\Component\Messenger\Envelope;
|
|||||||
*/
|
*/
|
||||||
interface RetryStrategyInterface
|
interface RetryStrategyInterface
|
||||||
{
|
{
|
||||||
public function isRetryable(Envelope $message): bool;
|
/**
|
||||||
|
* @param \Throwable|null $throwable The cause of the failed handling
|
||||||
|
*/
|
||||||
|
public function isRetryable(Envelope $message/*, \Throwable $throwable = null*/): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param \Throwable|null $throwable The cause of the failed handling
|
||||||
|
*
|
||||||
* @return int The time to delay/wait in milliseconds
|
* @return int The time to delay/wait in milliseconds
|
||||||
*/
|
*/
|
||||||
public function getWaitingTime(Envelope $message): int;
|
public function getWaitingTime(Envelope $message/*, \Throwable $throwable = null*/): int;
|
||||||
}
|
}
|
||||||
|
@ -76,4 +76,41 @@ class SendFailedMessageForRetryListenerTest extends TestCase
|
|||||||
|
|
||||||
$listener->onMessageFailed($event);
|
$listener->onMessageFailed($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testEnvelopeIsSentToTransportOnRetryWithExceptionPassedToRetryStrategy()
|
||||||
|
{
|
||||||
|
$exception = new \Exception('no!');
|
||||||
|
$envelope = new Envelope(new \stdClass());
|
||||||
|
|
||||||
|
$sender = $this->createMock(SenderInterface::class);
|
||||||
|
$sender->expects($this->once())->method('send')->willReturnCallback(function (Envelope $envelope) {
|
||||||
|
/** @var DelayStamp $delayStamp */
|
||||||
|
$delayStamp = $envelope->last(DelayStamp::class);
|
||||||
|
/** @var RedeliveryStamp $redeliveryStamp */
|
||||||
|
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(DelayStamp::class, $delayStamp);
|
||||||
|
$this->assertSame(1000, $delayStamp->getDelay());
|
||||||
|
|
||||||
|
$this->assertInstanceOf(RedeliveryStamp::class, $redeliveryStamp);
|
||||||
|
$this->assertSame(1, $redeliveryStamp->getRetryCount());
|
||||||
|
|
||||||
|
return $envelope;
|
||||||
|
});
|
||||||
|
$senderLocator = $this->createMock(ContainerInterface::class);
|
||||||
|
$senderLocator->expects($this->once())->method('has')->willReturn(true);
|
||||||
|
$senderLocator->expects($this->once())->method('get')->willReturn($sender);
|
||||||
|
$retryStategy = $this->createMock(RetryStrategyInterface::class);
|
||||||
|
$retryStategy->expects($this->once())->method('isRetryable')->with($envelope, $exception)->willReturn(true);
|
||||||
|
$retryStategy->expects($this->once())->method('getWaitingTime')->with($envelope, $exception)->willReturn(1000);
|
||||||
|
$retryStrategyLocator = $this->createMock(ContainerInterface::class);
|
||||||
|
$retryStrategyLocator->expects($this->once())->method('has')->willReturn(true);
|
||||||
|
$retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStategy);
|
||||||
|
|
||||||
|
$listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator);
|
||||||
|
|
||||||
|
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
|
||||||
|
|
||||||
|
$listener->onMessageFailed($event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user