From 891a404a54cdc60f14fcddb1b0037889ad747163 Mon Sep 17 00:00:00 2001 From: Allison Guilhem Date: Tue, 17 Oct 2023 10:41:54 +1100 Subject: [PATCH] [Scheduler] Add failureEvent --- src/Symfony/Component/Scheduler/CHANGELOG.md | 1 + .../Scheduler/Event/FailureEvent.php | 57 +++++++++++++++++++ .../DispatchSchedulerEventListener.php | 40 ++++++++++--- src/Symfony/Component/Scheduler/Schedule.php | 8 +++ src/Symfony/Component/Scheduler/Scheduler.php | 16 +++++- .../DispatchSchedulerEventListenerTest.php | 12 ++++ 6 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 src/Symfony/Component/Scheduler/Event/FailureEvent.php diff --git a/src/Symfony/Component/Scheduler/CHANGELOG.md b/src/Symfony/Component/Scheduler/CHANGELOG.md index 5514e3a952759..e166a5d2f6b53 100644 --- a/src/Symfony/Component/Scheduler/CHANGELOG.md +++ b/src/Symfony/Component/Scheduler/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * Add `MessageProviderInterface` to trigger unique messages at runtime * Add `PreRunEvent` and `PostRunEvent` events * Add `DispatchSchedulerEventListener` + * Add `FailureEvent` event 6.3 --- diff --git a/src/Symfony/Component/Scheduler/Event/FailureEvent.php b/src/Symfony/Component/Scheduler/Event/FailureEvent.php new file mode 100644 index 0000000000000..75f6ecbd63c4e --- /dev/null +++ b/src/Symfony/Component/Scheduler/Event/FailureEvent.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Scheduler\Event; + +use Symfony\Component\Scheduler\Generator\MessageContext; +use Symfony\Component\Scheduler\ScheduleProviderInterface; + +class FailureEvent +{ + private bool $shouldIgnore = false; + + public function __construct( + private readonly ScheduleProviderInterface $schedule, + private readonly MessageContext $messageContext, + private readonly object $message, + private readonly \Throwable $error, + ) { + } + + public function getMessageContext(): MessageContext + { + return $this->messageContext; + } + + public function getSchedule(): ScheduleProviderInterface + { + return $this->schedule; + } + + public function getMessage(): object + { + return $this->message; + } + + public function getError(): \Throwable + { + return $this->error; + } + + public function shouldIgnore(bool $shouldIgnore = null): bool + { + if (null !== $shouldIgnore) { + $this->shouldIgnore = $shouldIgnore; + } + + return $this->shouldIgnore; + } +} diff --git a/src/Symfony/Component/Scheduler/EventListener/DispatchSchedulerEventListener.php b/src/Symfony/Component/Scheduler/EventListener/DispatchSchedulerEventListener.php index a71e093e55d30..cdb0ebae738b7 100644 --- a/src/Symfony/Component/Scheduler/EventListener/DispatchSchedulerEventListener.php +++ b/src/Symfony/Component/Scheduler/EventListener/DispatchSchedulerEventListener.php @@ -14,8 +14,12 @@ use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; +use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Scheduler\Event\FailureEvent; use Symfony\Component\Scheduler\Event\PostRunEvent; use Symfony\Component\Scheduler\Event\PreRunEvent; use Symfony\Component\Scheduler\Messenger\ScheduledStamp; @@ -31,11 +35,8 @@ public function __construct( public function onMessageHandled(WorkerMessageHandledEvent $event): void { $envelope = $event->getEnvelope(); - if (!$scheduledStamp = $envelope->last(ScheduledStamp::class)) { - return; - } - if (!$this->scheduleProviderLocator->has($scheduledStamp->messageContext->name)) { + if (!$scheduledStamp = $this->getScheduledStamp($envelope)) { return; } @@ -46,11 +47,7 @@ public function onMessageReceived(WorkerMessageReceivedEvent $event): void { $envelope = $event->getEnvelope(); - if (!$scheduledStamp = $envelope->last(ScheduledStamp::class)) { - return; - } - - if (!$this->scheduleProviderLocator->has($scheduledStamp->messageContext->name)) { + if (!$scheduledStamp = $this->getScheduledStamp($envelope)) { return; } @@ -63,11 +60,36 @@ public function onMessageReceived(WorkerMessageReceivedEvent $event): void } } + public function onMessageFailed(WorkerMessageFailedEvent $event): void + { + $envelope = $event->getEnvelope(); + + if (!$scheduledStamp = $this->getScheduledStamp($envelope)) { + return; + } + + $this->eventDispatcher->dispatch(new FailureEvent($this->scheduleProviderLocator->get($scheduledStamp->messageContext->name), $scheduledStamp->messageContext, $envelope->getMessage(), $event->getThrowable())); + } + + private function getScheduledStamp(Envelope $envelope): ?StampInterface + { + if (!$scheduledStamp = $envelope->last(ScheduledStamp::class)) { + return null; + } + + if (!$this->scheduleProviderLocator->has($scheduledStamp->messageContext->name)) { + return null; + } + + return $scheduledStamp; + } + public static function getSubscribedEvents(): array { return [ WorkerMessageReceivedEvent::class => ['onMessageReceived'], WorkerMessageHandledEvent::class => ['onMessageHandled'], + WorkerMessageFailedEvent::class => ['onMessageFailed'], ]; } } diff --git a/src/Symfony/Component/Scheduler/Schedule.php b/src/Symfony/Component/Scheduler/Schedule.php index f784e34336780..4ccd88ff7a6e9 100644 --- a/src/Symfony/Component/Scheduler/Schedule.php +++ b/src/Symfony/Component/Scheduler/Schedule.php @@ -13,6 +13,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Scheduler\Event\FailureEvent; use Symfony\Component\Scheduler\Event\PostRunEvent; use Symfony\Component\Scheduler\Event\PreRunEvent; use Symfony\Component\Scheduler\Exception\LogicException; @@ -152,6 +153,13 @@ public function after(callable $listener, int $priority = 0): static return $this; } + public function onFailure(callable $listener, int $priority = 0): static + { + $this->dispatcher->addListener(FailureEvent::class, $listener, $priority); + + return $this; + } + public function shouldRestart(): bool { return $this->shouldRestart; diff --git a/src/Symfony/Component/Scheduler/Scheduler.php b/src/Symfony/Component/Scheduler/Scheduler.php index 4b6ecc285fa6f..dd278cd1caf99 100644 --- a/src/Symfony/Component/Scheduler/Scheduler.php +++ b/src/Symfony/Component/Scheduler/Scheduler.php @@ -14,6 +14,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Clock\Clock; use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Scheduler\Event\FailureEvent; use Symfony\Component\Scheduler\Event\PostRunEvent; use Symfony\Component\Scheduler\Event\PreRunEvent; use Symfony\Component\Scheduler\Generator\MessageGenerator; @@ -81,10 +82,19 @@ public function run(array $options = []): void continue; } - $this->handlers[$message::class]($message); - $ran = true; + try { + $this->handlers[$message::class]($message); + $ran = true; - $this->dispatcher->dispatch(new PostRunEvent($generator->getSchedule(), $context, $message)); + $this->dispatcher->dispatch(new PostRunEvent($generator->getSchedule(), $context, $message)); + } catch (\Throwable $error) { + $failureEvent = new FailureEvent($generator->getSchedule(), $context, $message, $error); + $this->dispatcher->dispatch($failureEvent); + + if (!$failureEvent->shouldIgnore()) { + throw $error; + } + } } } diff --git a/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php b/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php index 53a933358032a..0a12e8a1b4029 100644 --- a/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php +++ b/src/Symfony/Component/Scheduler/Tests/EventListener/DispatchSchedulerEventListenerTest.php @@ -15,8 +15,10 @@ use Psr\Container\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; +use Symfony\Component\Scheduler\Event\FailureEvent; use Symfony\Component\Scheduler\Event\PostRunEvent; use Symfony\Component\Scheduler\Event\PreRunEvent; use Symfony\Component\Scheduler\EventListener\DispatchSchedulerEventListener; @@ -45,15 +47,19 @@ public function testDispatchSchedulerEvents() $listener = new DispatchSchedulerEventListener($scheduleProviderLocator, $eventDispatcher = new EventDispatcher()); $workerReceivedEvent = new WorkerMessageReceivedEvent($envelope, 'default'); $workerHandledEvent = new WorkerMessageHandledEvent($envelope, 'default'); + $workerFailedEvent = new WorkerMessageFailedEvent($envelope, 'default', new \Exception()); $secondListener = new TestEventListener(); $eventDispatcher->addListener(PreRunEvent::class, [$secondListener, 'preRun']); $eventDispatcher->addListener(PostRunEvent::class, [$secondListener, 'postRun']); + $eventDispatcher->addListener(FailureEvent::class, [$secondListener, 'onFailure']); $listener->onMessageReceived($workerReceivedEvent); $listener->onMessageHandled($workerHandledEvent); + $listener->onMessageFailed($workerFailedEvent); $this->assertTrue($secondListener->preInvoked); $this->assertTrue($secondListener->postInvoked); + $this->assertTrue($secondListener->failureInvoked); } } @@ -62,6 +68,7 @@ class TestEventListener public string $name; public bool $preInvoked = false; public bool $postInvoked = false; + public bool $failureInvoked = false; /* Listener methods */ @@ -74,4 +81,9 @@ public function postRun($e) { $this->postInvoked = true; } + + public function onFailure($e) + { + $this->failureInvoked = true; + } }