diff --git a/src/Symfony/Component/Messenger/Tests/WorkerTest.php b/src/Symfony/Component/Messenger/Tests/WorkerTest.php index 55c28357d1f48..cb2d4ef9e51c4 100644 --- a/src/Symfony/Component/Messenger/Tests/WorkerTest.php +++ b/src/Symfony/Component/Messenger/Tests/WorkerTest.php @@ -41,6 +41,7 @@ use Symfony\Component\Messenger\Stamp\SentStamp; use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Messenger\Tests\Fixtures\DummyReceiver; use Symfony\Component\Messenger\Tests\Fixtures\ResettableDummyReceiver; use Symfony\Component\Messenger\Transport\Receiver\QueueReceiverInterface; @@ -581,6 +582,52 @@ public function testFlushBatchOnStop() $this->assertSame($expectedMessages, $handler->processedMessages); } + public function testFlushMultipleBatchOnStop() + { + $expectedMessages = [ + new DummyMessage('Hey'), + ]; + + $secondHandlerExpectedMessages = [ + new SecondHandlerDummyMessage('Ho'), + ]; + + $receiver = new DummyReceiver([ + [new Envelope($expectedMessages[0])], + ]); + + $secondHandlerReceiver = new SecondMessageDummyReceiver([ + [new Envelope($secondHandlerExpectedMessages[0])], + ]); + + $handler = new DummyBatchHandler(); + $secondHandler = new SecondDummyBatchHandler(); + + $middleware = new HandleMessageMiddleware(new HandlersLocator([ + DummyMessage::class => [new HandlerDescriptor($handler)], + SecondHandlerDummyMessage::class => [new HandlerDescriptor($secondHandler)], + ])); + + $bus = new MessageBus([$middleware]); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(WorkerRunningEvent::class, function (WorkerRunningEvent $event) use ($receiver, $secondHandlerReceiver) { + static $i = 0; + if (1 < ++$i) { + $event->getWorker()->stop(); + } + + $this->assertSame(0, $receiver->getAcknowledgeCount()); + $this->assertSame(0, $secondHandlerReceiver->getAcknowledgeCount()); + }); + + $worker = new Worker([$receiver, $secondHandlerReceiver], $bus, $dispatcher, clock: new MockClock()); + $worker->run(); + + $this->assertSame($expectedMessages, $handler->processedMessages); + $this->assertSame($secondHandlerExpectedMessages, $secondHandler->processedMessages); + } + public function testGcCollectCyclesIsCalledOnMessageHandle() { $apiMessage = new DummyMessage('API'); @@ -634,3 +681,48 @@ private function process(array $jobs): void } } } + +Class SecondDummyBatchHandler implements BatchHandlerInterface +{ + use BatchHandlerTrait; + + public array $processedMessages; + + public function __invoke(SecondHandlerDummyMessage $message, ?Acknowledger $ack = null) + { + return $this->handle($message, $ack); + } + + private function shouldFlush(): bool + { + return 5 <= \count($this->jobs); + } + + private function process(array $jobs): void + { + $this->processedMessages = array_column($jobs, 0); + + foreach ($jobs as [$job, $ack]) { + $ack->ack($job); + } + } +} + +class SecondHandlerDummyMessage implements DummyMessageInterface +{ + private string $message; + + public function __construct(string $message) + { + $this->message = $message; + } + + public function getMessage(): string + { + return $this->message; + } +} + +class SecondMessageDummyReceiver extends DummyReceiver +{ +} diff --git a/src/Symfony/Component/Messenger/Worker.php b/src/Symfony/Component/Messenger/Worker.php index 3d69dd6135190..b4ce621f03dc1 100644 --- a/src/Symfony/Component/Messenger/Worker.php +++ b/src/Symfony/Component/Messenger/Worker.php @@ -254,15 +254,17 @@ private function flush(bool $force): bool $this->unacks = new \SplObjectStorage(); - foreach ($unacks as $batchHandler) { + while ($unacks->valid()) { + $batchHandler = $unacks->current(); [$envelope, $transportName] = $unacks[$batchHandler]; try { $this->bus->dispatch($envelope->with(new FlushBatchHandlersStamp($force))); $envelope = $envelope->withoutAll(NoAutoAckStamp::class); - unset($unacks[$batchHandler], $batchHandler); } catch (\Throwable $e) { $this->acks[] = [$transportName, $envelope, $e]; } + $unacks->next(); + $unacks->detach($batchHandler); } return $this->ack();