vendor/symfony/messenger/EventListener/SendFailedMessageForRetryListener.php line 43

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Messenger\EventListener;
  11. use Psr\Container\ContainerInterface;
  12. use Psr\Log\LoggerInterface;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. use Symfony\Component\Messenger\Envelope;
  15. use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
  16. use Symfony\Component\Messenger\Exception\HandlerFailedException;
  17. use Symfony\Component\Messenger\Exception\RecoverableExceptionInterface;
  18. use Symfony\Component\Messenger\Exception\RuntimeException;
  19. use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
  20. use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
  21. use Symfony\Component\Messenger\Stamp\DelayStamp;
  22. use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
  23. use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
  24. /**
  25.  * @author Tobias Schultze <http://tobion.de>
  26.  */
  27. class SendFailedMessageForRetryListener implements EventSubscriberInterface
  28. {
  29.     private $sendersLocator;
  30.     private $retryStrategyLocator;
  31.     private $logger;
  32.     public function __construct(ContainerInterface $sendersLocatorContainerInterface $retryStrategyLocatorLoggerInterface $logger null)
  33.     {
  34.         $this->sendersLocator $sendersLocator;
  35.         $this->retryStrategyLocator $retryStrategyLocator;
  36.         $this->logger $logger;
  37.     }
  38.     public function onMessageFailed(WorkerMessageFailedEvent $event)
  39.     {
  40.         $retryStrategy $this->getRetryStrategyForTransport($event->getReceiverName());
  41.         $envelope $event->getEnvelope();
  42.         $throwable $event->getThrowable();
  43.         $message $envelope->getMessage();
  44.         $context = [
  45.             'message' => $message,
  46.             'class' => \get_class($message),
  47.         ];
  48.         $shouldRetry $retryStrategy && $this->shouldRetry($throwable$envelope$retryStrategy);
  49.         $retryCount RedeliveryStamp::getRetryCountFromEnvelope($envelope);
  50.         if ($shouldRetry) {
  51.             $event->setForRetry();
  52.             ++$retryCount;
  53.             $delay $retryStrategy->getWaitingTime($envelope$throwable);
  54.             if (null !== $this->logger) {
  55.                 $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]);
  56.             }
  57.             // add the delay and retry stamp info
  58.             $retryEnvelope $envelope->with(new DelayStamp($delay), new RedeliveryStamp($retryCount));
  59.             // re-send the message for retry
  60.             $this->getSenderForTransport($event->getReceiverName())->send($retryEnvelope);
  61.         } else {
  62.             if (null !== $this->logger) {
  63.                 $this->logger->critical('Error thrown while handling message {class}. Removing from transport after {retryCount} retries. Error: "{error}"'$context + ['retryCount' => $retryCount'error' => $throwable->getMessage(), 'exception' => $throwable]);
  64.             }
  65.         }
  66.     }
  67.     public static function getSubscribedEvents()
  68.     {
  69.         return [
  70.             // must have higher priority than SendFailedMessageToFailureTransportListener
  71.             WorkerMessageFailedEvent::class => ['onMessageFailed'100],
  72.         ];
  73.     }
  74.     private function shouldRetry(\Throwable $eEnvelope $envelopeRetryStrategyInterface $retryStrategy): bool
  75.     {
  76.         if ($e instanceof RecoverableExceptionInterface) {
  77.             return true;
  78.         }
  79.         // if one or more nested Exceptions is an instance of RecoverableExceptionInterface we should retry
  80.         // if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry
  81.         if ($e instanceof HandlerFailedException) {
  82.             $shouldNotRetry true;
  83.             foreach ($e->getNestedExceptions() as $nestedException) {
  84.                 if ($nestedException instanceof RecoverableExceptionInterface) {
  85.                     return true;
  86.                 }
  87.                 if (!$nestedException instanceof UnrecoverableExceptionInterface) {
  88.                     $shouldNotRetry false;
  89.                     break;
  90.                 }
  91.             }
  92.             if ($shouldNotRetry) {
  93.                 return false;
  94.             }
  95.         }
  96.         if ($e instanceof UnrecoverableExceptionInterface) {
  97.             return false;
  98.         }
  99.         return $retryStrategy->isRetryable($envelope$e);
  100.     }
  101.     private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
  102.     {
  103.         if ($this->retryStrategyLocator->has($alias)) {
  104.             return $this->retryStrategyLocator->get($alias);
  105.         }
  106.         return null;
  107.     }
  108.     private function getSenderForTransport(string $alias): SenderInterface
  109.     {
  110.         if ($this->sendersLocator->has($alias)) {
  111.             return $this->sendersLocator->get($alias);
  112.         }
  113.         throw new RuntimeException(sprintf('Could not find sender "%s" based on the same receiver to send the failed message to for retry.'$alias));
  114.     }
  115. }