Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Messenger] add a transport that processes messages on kernel.terminate #28746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

[Messenger] add a transport that processes messages on kernel.terminate #28746

wants to merge 1 commit into from

Conversation

gonzalovilaseca
Copy link
Contributor

@gonzalovilaseca gonzalovilaseca commented Oct 5, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #28646
License MIT
Doc PR

I'm not sure if this is what Nicolas was asking for in this issue: #28646 (comment)
If it is, there are some questions that need general consensus:

  • Is it worth splitting this file into a listener and a handler and inject the handler in the listener?
  • Should the listener be in messenger component, in framework bundle or where?
  • Should the service definition: eg: tags for being processed as a handler and as an event subscriber be documented and configured manually by the user or should we put some kind of automation in place?

@gonzalovilaseca
Copy link
Contributor Author

(Deprecation label should be removed BTW)

@sroze
Copy link
Contributor

sroze commented Oct 6, 2018

I think the approach of having a decorated handler might not be the best. What about using a transport for this?

@chalasr chalasr changed the title #28646 [Messenger] add a handler that processes messages on kernel.terminate [Messenger] add a handler that processes messages on kernel.terminate Oct 8, 2018
@gonzalovilaseca
Copy link
Contributor Author

@sroze Not sure if I got it right....is this the right approach? If it is, I'll create tests, documentation etc.

@ogizanagi
Copy link
Contributor

ogizanagi commented Oct 11, 2018

Just for the sake of completeness, here is the whole workflow to me:

  1. A message routed to the kernel.terminate transport is dispatched.
  2. The SendMessageMiddleware gets the associated transport and send the message
  3. KernelTerminateTransport::send() stores the message to be treated later.
  4. If an exception occurs during the request, a kernel.exception listener clears the spool of messages, so none are handled.
  5. On kernel.terminate, KernelTerminateTransport::flush() is executed,
    which calls KernelTerminateTransport::receive() with the callable $handler(?Envelope $envelope) argument:
    • adding a ReceivedMessage item to the envelope so the message is not sent again by the SendMessageMiddleware
    • dispatching it to the bus.
  6. KernelTerminateTransport::receive() loops over stored messages and call $handler for each of them. Thus, each message is dispatched to be finally handled.

I'm just not sure about 4. Perhaps it should be configurable at the transport level as an option.

@nicolas-grekas nicolas-grekas added this to the next milestone Oct 14, 2018
@@ -1553,6 +1554,11 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
->addTag('messenger.receiver', array('alias' => $name))
->addTag('messenger.sender', array('alias' => $name))
;

if ('symfony://kernel.terminate' === $transport) {
$transportDefinition->addTag('kernel.event_listener', array('event' => KernelEvents::TERMINATE));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you take the event from what is after symfony:// instead of hardcoding it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense if we're only going to listen to that event?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments after #28646 (comment).
I'm not convinced another use-case should be provided out-of-the-box by the Framework bundle.

@nicolas-grekas
Copy link
Member

See #28646 (comment) for more thoughts on this.

@gonzalovilaseca
Copy link
Contributor Author

I've updated the PR as per the comments (hope I understood all comments correctly...).
I'd like to ask for feedback on this issues:

  1. I'd split MemoryTransport into 2 classes MemoryTransport and SymfonyKernelEventTransport, the second one will contain MemoryTransport plus the 2 kernel listener methods.
  2. I also need to pass the bus to the memory transport but I'm not sure what's the best way to achieve it, it could be defined in the transport options, but then I'd need to inject it into the factory in src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php, the factory would then inject it in the memory transport. Any better way?

@gonzalovilaseca
Copy link
Contributor Author

BTW does anyone have a sandbox project I could use to test this functionality?

@gonzalovilaseca gonzalovilaseca changed the title [Messenger] add a handler that processes messages on kernel.terminate [Messenger] add a transport that processes messages on kernel.terminate Oct 28, 2018
@gonzalovilaseca
Copy link
Contributor Author

@sroze @nicolas-grekas @ogizanagi Have updated the PR with suggestions given, it's not ready to review yet as it's not complete, but would like some feedback to check if I'm in the right direction before doing the final work.
Thanks!

@ogizanagi
Copy link
Contributor

@gonzalovilaseca : Sorry for the lack of answer.

I also need to pass the bus to the memory transport but I'm not sure what's the best way to achieve it

I think the factory should be aware of all the available buses, by injecting a service locator. Then, a transport option should allow the factory to select the right bus by its key.

@gonzalovilaseca
Copy link
Contributor Author

@gonzalovilaseca : Sorry for the lack of answer.

I also need to pass the bus to the memory transport but I'm not sure what's the best way to achieve it

I think the factory should be aware of all the available buses, by injecting a service locator. Then, a transport option should allow the factory to select the right bus by its key.

Can you check what I've done in src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php? I've just defined an alias for the chosen bus, it would make sense to inject a service locator if the transport was able to use more than one, I think it's easier WDYT?

@ogizanagi
Copy link
Contributor

ogizanagi commented Oct 28, 2018

You may have more than one of such transport. For instance one per bus. The factory would be used to create each of these then (so it needs to know every buses).

@gonzalovilaseca
Copy link
Contributor Author

updated!

@gonzalovilaseca
Copy link
Contributor Author

@sroze @nicolas-grekas @ogizanagi Are we happy with this as it is, so I can start writing tests?

Copy link
Contributor

@ogizanagi ogizanagi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good (to me) yes :)

@gonzalovilaseca
Copy link
Contributor Author

Updated!

@ogizanagi
Copy link
Contributor

ogizanagi commented Oct 31, 2018

@gonzalovilaseca: I hope you won't mind me, but I've rebased and pushed some changes to your branch hopefully fixing @sroze's review and some other tweaks I've spotted during this operation :)

@@ -1568,6 +1576,21 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
->setArguments(array($transport['dsn'], $transport['options']))
->addTag('messenger.receiver', array('alias' => $name))
;

if (0 === strpos($transport['dsn'], 'symfony://kernel.terminate')) {
Copy link
Contributor

@ogizanagi ogizanagi Oct 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I just realized this won't work with env vars.
As I don't see any use-cases for it, it may be fine, but we need extra pov.
If we want to support it, the kernel terminate transport factory will need the event dispatcher and add listeners itself (still, that would be a better implementation and give the factory even more sense?).

(Note the same goes for the amqp:// check above, but it's only about a hint for the developper)

@gonzalovilaseca
Copy link
Contributor Author

@ogizanagi @sroze I've updated the PR with tests

@ogizanagi
Copy link
Contributor

@gonzalovilaseca : Great! :)

But I think we need to treat #28746 (comment). Consistency is the key, this transport dsn should work as an env var as any other.
The default recipe suggests a MESSENGER_TRANSPORT_DSN env var that one may use as MESSENGER_TRANSPORT_DSN=symfony://kernel.terminate.
This means the factory must register the event listener itself at runtime, not the extension.

Regarding #29097 (comment), I also think we should extract the re-dispatching logic out of the MemoryTransport, which would just keep messages in memory, no more. So the null:// transport would just be a different factory.
But this last point could be done in a next PR, as we have time to iterate over this feature.

*/
public function stop(): void
{
$this->envelopes = array();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of emptying, we may just use a $stopped flag used in receive to skip handling as in

so we keep envelopes list on exception, but don't re-dispatch them.

@gonzalovilaseca
Copy link
Contributor Author

Can an event be registered at runtime? (I thought that wasnt possible as the container is already compiled)

@ogizanagi
Copy link
Contributor

Sure. You'll need to inject the @event_dispatcher in the factory, and use EventDispatcher::addListener.
The container being already compiled just means we won't see the tagged service definition using debug:container (nor debug:event-dispatcher unless the factory is called before).

@gonzalovilaseca
Copy link
Contributor Author

Ok cool, will do that then.

@gonzalovilaseca
Copy link
Contributor Author

@ogizanagi updated following suggestions

public function flush(): void
{
$this->receive(function (Envelope $envelope) {
if (null === $envelope) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$envelope can not be null here, because of the type hint.

$transport = new MemoryTransport($this->busLocator->get($busId));

$this->eventDispatcher->addListener(KernelEvents::TERMINATE, array($transport, 'flush'));
$this->eventDispatcher->addListener(KernelEvents::EXCEPTION, array($transport, 'stop'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also have to add listeners to the corresponding console events? Like in swiftmailer-bundle for the memory spool?

@gharlan
Copy link
Contributor

gharlan commented Mar 14, 2019

Any chance to get this merged into 4.3? (feature freeze is in two weeks)
How can I help?

@gonzalovilaseca
Copy link
Contributor Author

It's just waiting for review..

@sroze
Copy link
Contributor

sroze commented Apr 6, 2019

Thank you very much for your efforts on this pull-request @gonzalovilaseca. Now that we have other transports (Doctrine especially) I think that the risks associated with such transport are too high: as mentioned by @fabpot ealier, running things on kernel.terminate are making things very hard to debug because you won't be able to see the errors (if some happen), even in development, nor be able to benefit from the retry mechanism that has been introduced.

Let's close this so we push everybody to use more reliable solutions 🚀

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 6, 2019

Sorry for making you work on this, but even if it wasn't merged, it helped a lot moving things forward! So thank you!

@nicolas-grekas nicolas-grekas modified the milestones: next, 4.3 Apr 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants