From 488bb883ceef8e82539d2fc31f991a3c29fa3f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 10 May 2021 17:37:05 +0200 Subject: [PATCH] [Mesenger] Add support for resetting container services after each messenger message. Without this patch, services are not resetted. For example Monolog Finger Cross handler is never reset nor flushed. So if the first message trigger and "error" level message, all others message will log and overflow the buffer. Usage with framework: ```yaml framework: messenger: transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' reset_on_message: true failed: 'doctrine://default?queue_name=failed' sync: 'sync://' ``` --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 4 ++ .../FrameworkExtension.php | 13 +++++ .../Resources/config/messenger.php | 7 +++ .../Resources/config/schema/symfony-1.0.xsd | 1 + .../Fixtures/php/messenger_transports.php | 1 + .../Fixtures/xml/messenger_transports.xml | 2 +- .../Fixtures/yml/messenger_transports.yml | 1 + .../FrameworkExtensionTest.php | 4 ++ src/Symfony/Component/Messenger/CHANGELOG.md | 1 + .../EventListener/ResetServicesListener.php | 52 +++++++++++++++++++ .../ResetServicesListenerTest.php | 39 ++++++++++++++ 12 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Messenger/EventListener/ResetServicesListener.php create mode 100644 src/Symfony/Component/Messenger/Tests/EventListener/ResetServicesListenerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 4d89c1bffc00c..4e30aa5394084 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Deprecate `get()`, `has()`, `getDoctrine()`, and `dispatchMessage()` in `AbstractController`, use method/constructor injection instead * Add `MicroKernelTrait::getBundlesPath` method to get bundles config path * Deprecate the `cache.adapter.doctrine` service + * Add support for resetting container services after each messenger message. 5.3 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index d52b8dfb0dcb9..52f81c0a32421 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1333,6 +1333,10 @@ function ($a) { ->fixXmlConfig('option') ->children() ->scalarNode('dsn')->end() + ->booleanNode('reset_on_message') + ->defaultFalse() + ->info('Reset container services after each message. Turn it on when the transport is async and run in a worker.') + ->end() ->scalarNode('serializer')->defaultNull()->info('Service id of a custom serializer to use.')->end() ->arrayNode('options') ->normalizeKeys(false) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 04526e82932c0..6ddf0c3a053a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2013,6 +2013,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $senderAliases = []; $transportRetryReferences = []; + $transportNamesForResetServices = []; foreach ($config['transports'] as $name => $transport) { $serializerId = $transport['serializer'] ?? 'messenger.default_serializer'; $transportDefinition = (new Definition(TransportInterface::class)) @@ -2041,6 +2042,18 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $transportRetryReferences[$name] = new Reference($retryServiceId); } + if ($transport['reset_on_message']) { + $transportNamesForResetServices[] = $name; + } + } + + if ($transportNamesForResetServices) { + $container + ->getDefinition('messenger.listener.reset_services') + ->replaceArgument(1, $transportNamesForResetServices) + ; + } else { + $container->removeDefinition('messenger.listener.reset_services'); } $senderReferences = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index d7953122fbe51..c15294c38ccda 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -18,6 +18,7 @@ use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; use Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener; use Symfony\Component\Messenger\EventListener\DispatchPcntlSignalListener; +use Symfony\Component\Messenger\EventListener\ResetServicesListener; use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener; use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener; use Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener; @@ -195,6 +196,12 @@ ->tag('kernel.event_subscriber') ->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class) + + ->set('messenger.listener.reset_services', ResetServicesListener::class) + ->args([ + service('services_resetter'), + abstract_arg('receivers names'), + ]) ->tag('kernel.event_subscriber') ->set('messenger.routable_message_bus', RoutableMessageBus::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 5082c3356a673..47c7edf34f7d5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -505,6 +505,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php index 90c5def3ac100..1c8b56683d2e5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php @@ -11,6 +11,7 @@ 'default' => 'amqp://localhost/%2f/messages', 'customised' => [ 'dsn' => 'amqp://localhost/%2f/messages?exchange_name=exchange_name', + 'reset_on_message' => true, 'options' => ['queue' => ['name' => 'Queue']], 'serializer' => 'messenger.transport.native_php_serializer', 'retry_strategy' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml index b0510d580ceaf..dfa3232997c52 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml @@ -10,7 +10,7 @@ - + Queue diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml index d00f4a65dd37c..fb2827729d5e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml @@ -8,6 +8,7 @@ framework: default: 'amqp://localhost/%2f/messages' customised: dsn: 'amqp://localhost/%2f/messages?exchange_name=exchange_name' + reset_on_message: true options: queue: name: Queue diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 3315664ec80ba..15aee24a5b701 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -723,6 +723,7 @@ public function testMessenger() $this->assertTrue($container->hasDefinition('messenger.transport.redis.factory')); $this->assertTrue($container->hasDefinition('messenger.transport_factory')); $this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass()); + $this->assertFalse($container->hasDefinition('messenger.listener.reset_services')); } public function testMessengerMultipleFailureTransports() @@ -867,6 +868,9 @@ public function testMessengerTransports() return array_shift($values); }, $failureTransports); $this->assertEquals($expectedTransportsByFailureTransports, $failureTransportsReferences); + + $this->assertTrue($container->hasDefinition('messenger.listener.reset_services')); + $this->assertSame(['customised'], $container->getDefinition('messenger.listener.reset_services')->getArgument(1)); } public function testMessengerRouting() diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index fed0bc8b76f93..889207a17400d 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `StopWorkerExceptionInterface` and its implementation `StopWorkerException` to stop the worker. + * Add support for resetting container services after each messenger message. 5.3 --- diff --git a/src/Symfony/Component/Messenger/EventListener/ResetServicesListener.php b/src/Symfony/Component/Messenger/EventListener/ResetServicesListener.php new file mode 100644 index 0000000000000..19b7ebe94c967 --- /dev/null +++ b/src/Symfony/Component/Messenger/EventListener/ResetServicesListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; +use Symfony\Component\Messenger\Event\AbstractWorkerMessageEvent; +use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; +use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; +use Symfony\Component\Messenger\Event\WorkerRunningEvent; + +/** + * @author Grégoire Pineau + */ +class ResetServicesListener implements EventSubscriberInterface +{ + private $servicesResetter; + private $receiversName; + + public function __construct(ServicesResetter $servicesResetter, array $receiversName) + { + $this->servicesResetter = $servicesResetter; + $this->receiversName = $receiversName; + } + + public function resetServices(AbstractWorkerMessageEvent $event) + { + if (!\in_array($event->getReceiverName(), $this->receiversName, true)) { + return; + } + + $this->servicesResetter->reset(); + } + + public static function getSubscribedEvents() + { + return [ + WorkerMessageHandledEvent::class => ['resetServices'], + WorkerMessageFailedEvent::class => ['resetServices'], + WorkerRunningEvent::class => ['resetServices'], + ]; + } +} diff --git a/src/Symfony/Component/Messenger/Tests/EventListener/ResetServicesListenerTest.php b/src/Symfony/Component/Messenger/Tests/EventListener/ResetServicesListenerTest.php new file mode 100644 index 0000000000000..a14fe113cfde3 --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/EventListener/ResetServicesListenerTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Event\AbstractWorkerMessageEvent; +use Symfony\Component\Messenger\EventListener\ResetServicesListener; + +class ResetServicesListenerTest extends TestCase +{ + public function provideTests(): iterable + { + yield ['foo', true]; + yield ['bar', false]; + } + + /** @dataProvider provideTests */ + public function test(string $receiverName, bool $shouldReset) + { + $servicesResetter = $this->createMock(ServicesResetter::class); + $servicesResetter->expects($shouldReset ? $this->once() : $this->never())->method('reset'); + + $event = new class(new Envelope(new \stdClass()), $receiverName) extends AbstractWorkerMessageEvent {}; + + $resetListener = new ResetServicesListener($servicesResetter, ['foo']); + $resetListener->resetServices($event); + } +}