diff --git a/core/lib/Event/EventDispatcher.php b/core/lib/Event/EventDispatcher.php index cf024a2..3871a04 100644 --- a/core/lib/Event/EventDispatcher.php +++ b/core/lib/Event/EventDispatcher.php @@ -60,34 +60,34 @@ final class EventDispatcher implements EventDispatcherInterface, DeferredEventPr throw new \LogicException('Cannot process deferred events for an inactive execution.'); } - $processed = 0; - $deadline = microtime(true) + 1.0; - $deadlineExceeded = false; - $limitExceeded = false; - while (($event = array_shift($this->deferred[$executionId])) !== null) { - if ($processed >= 1000) { - $limitExceeded = true; - array_unshift($this->deferred[$executionId], $event); - break; + try { + $processed = 0; + $deadline = microtime(true) + 1.0; + $deadlineExceeded = false; + $limitExceeded = false; + while (($event = array_shift($this->deferred[$executionId])) !== null) { + if ($processed >= 1000) { + $limitExceeded = true; + array_unshift($this->deferred[$executionId], $event); + break; + } + if (microtime(true) >= $deadline) { + $deadlineExceeded = true; + array_unshift($this->deferred[$executionId], $event); + break; + } + $processed += $this->invoke($event, DeliveryMode::Deferred); } - if (microtime(true) >= $deadline) { - $deadlineExceeded = true; - array_unshift($this->deferred[$executionId], $event); - break; - } - $processed += $this->invoke($event, DeliveryMode::Deferred); + + return new DeferredProcessingResult( + $processed, + count($this->deferred[$executionId]), + $deadlineExceeded, + $limitExceeded, + ); + } finally { + $this->discardDeferred($executionId); } - - $remaining = count($this->deferred[$executionId]); - unset($this->deferred[$executionId]); - $this->activeExecution = null; - - return new DeferredProcessingResult( - $processed, - $remaining, - $deadlineExceeded, - $limitExceeded, - ); } public function discardDeferred(string $executionId): void diff --git a/tests/php/Unit/Event/EventDispatcherTest.php b/tests/php/Unit/Event/EventDispatcherTest.php index 83606a1..ba4310c 100644 --- a/tests/php/Unit/Event/EventDispatcherTest.php +++ b/tests/php/Unit/Event/EventDispatcherTest.php @@ -167,6 +167,43 @@ final class EventDispatcherTest extends TestCase $dispatcher->dispatch(new Event('test.event')); } + #[Test] + #[TestDox('Propagated deferred failures close their execution scope')] + public function recoversFromDeferredFailure(): void + { + $registry = new EventListenerRegistry(); + $registry->listen( + 'test', + 'test.event', + FailingListener::class, + 'fail', + DeliveryMode::Deferred, + failurePolicy: FailurePolicy::Propagate, + ); + $registry->freeze(); + + $dispatcher = new EventDispatcher( + $registry, + new RecordingContainer([FailingListener::class => new FailingListener()]), + new NullLogger(), + ); + $dispatcher->beginExecution('failed'); + $dispatcher->dispatch(new Event('test.event')); + + try { + $dispatcher->processDeferred('failed'); + self::fail('Expected deferred listener failure to propagate.'); + } catch (\RuntimeException $error) { + self::assertSame('Listener failed.', $error->getMessage()); + } + + $dispatcher->beginExecution('next'); + $result = $dispatcher->processDeferred('next'); + + self::assertSame(0, $result->processed); + self::assertSame(0, $result->remaining); + } + #[Test] #[TestDox('Deferred processing stops at its configured count limit')] public function boundsDeferredWork(): void