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

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\RuntimeException;
  18. use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
  19. use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
  20. use Symfony\Component\Messenger\Stamp\DelayStamp;
  21. use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
  22. use Symfony\Component\Messenger\Stamp\StampInterface;
  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.     private $historySize;
  33.     public function __construct(ContainerInterface $sendersLocatorContainerInterface $retryStrategyLocatorLoggerInterface $logger nullint $historySize 10)
  34.     {
  35.         $this->sendersLocator $sendersLocator;
  36.         $this->retryStrategyLocator $retryStrategyLocator;
  37.         $this->logger $logger;
  38.         $this->historySize $historySize;
  39.     }
  40.     public function onMessageFailed(WorkerMessageFailedEvent $event)
  41.     {
  42.         $retryStrategy $this->getRetryStrategyForTransport($event->getReceiverName());
  43.         $envelope $event->getEnvelope();
  44.         $throwable $event->getThrowable();
  45.         $message $envelope->getMessage();
  46.         $context = [
  47.             'message' => $message,
  48.             'class' => \get_class($message),
  49.         ];
  50.         $shouldRetry $retryStrategy && $this->shouldRetry($throwable$envelope$retryStrategy);
  51.         $retryCount RedeliveryStamp::getRetryCountFromEnvelope($envelope);
  52.         if ($shouldRetry) {
  53.             $event->setForRetry();
  54.             ++$retryCount;
  55.             $delay $retryStrategy->getWaitingTime($envelope);
  56.             if (null !== $this->logger) {
  57.                 $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]);
  58.             }
  59.             // add the delay and retry stamp info
  60.             $retryEnvelope $this->withLimitedHistory($envelope, new DelayStamp($delay), new RedeliveryStamp($retryCount));
  61.             // re-send the message for retry
  62.             $this->getSenderForTransport($event->getReceiverName())->send($retryEnvelope);
  63.         } else {
  64.             if (null !== $this->logger) {
  65.                 $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]);
  66.             }
  67.         }
  68.     }
  69.     /**
  70.      * Adds stamps to the envelope by keeping only the First + Last N stamps.
  71.      */
  72.     private function withLimitedHistory(Envelope $envelopeStampInterface ...$stamps): Envelope
  73.     {
  74.         foreach ($stamps as $stamp) {
  75.             $history $envelope->all(\get_class($stamp));
  76.             if (\count($history) < $this->historySize) {
  77.                 $envelope $envelope->with($stamp);
  78.                 continue;
  79.             }
  80.             $history array_merge(
  81.                 [$history[0]],
  82.                 \array_slice($history, -$this->historySize 2),
  83.                 [$stamp]
  84.             );
  85.             $envelope $envelope->withoutAll(\get_class($stamp))->with(...$history);
  86.         }
  87.         return $envelope;
  88.     }
  89.     public static function getSubscribedEvents()
  90.     {
  91.         return [
  92.             // must have higher priority than SendFailedMessageToFailureTransportListener
  93.             WorkerMessageFailedEvent::class => ['onMessageFailed'100],
  94.         ];
  95.     }
  96.     private function shouldRetry(\Throwable $eEnvelope $envelopeRetryStrategyInterface $retryStrategy): bool
  97.     {
  98.         // if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry
  99.         if ($e instanceof HandlerFailedException) {
  100.             $shouldNotRetry true;
  101.             foreach ($e->getNestedExceptions() as $nestedException) {
  102.                 if (!$nestedException instanceof UnrecoverableExceptionInterface) {
  103.                     $shouldNotRetry false;
  104.                     break;
  105.                 }
  106.             }
  107.             if ($shouldNotRetry) {
  108.                 return false;
  109.             }
  110.         }
  111.         if ($e instanceof UnrecoverableExceptionInterface) {
  112.             return false;
  113.         }
  114.         return $retryStrategy->isRetryable($envelope);
  115.     }
  116.     private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
  117.     {
  118.         if ($this->retryStrategyLocator->has($alias)) {
  119.             return $this->retryStrategyLocator->get($alias);
  120.         }
  121.         return null;
  122.     }
  123.     private function getSenderForTransport(string $alias): SenderInterface
  124.     {
  125.         if ($this->sendersLocator->has($alias)) {
  126.             return $this->sendersLocator->get($alias);
  127.         }
  128.         throw new RuntimeException(sprintf('Could not find sender "%s" based on the same receiver to send the failed message to for retry.'$alias));
  129.     }
  130. }