Skip to content

[Messenger] multiple failure transports support #34979

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

Conversation

monteiro
Copy link
Contributor

@monteiro monteiro commented Dec 14, 2019

Q A
Branch? master
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #34911
License MIT
Doc PR symfony/symfony-docs#13489

Strategy applied

  • Pass a map of transports and failed transports to the SendFailedMessageToFailureTransportListener. This way we re-use the same listener.
  • Local failed transport has more priority than a global failed transport defined.

Configuration example

# config/packages/messenger.yaml
framework:
    # no need to set failed transport globally if I want a specific behaviour per transport.
    failure_transport: failed # all transports have this failed transport
    messenger:
        transports:
            failed: 'doctrine://default?queue_name=failed'
            failed_important: 'doctrine://default?queue_name=failed_important'
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                failure_transport: failed # takes precedence over the global defined "failed_transport"
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
            async_no_failure_transport: # it will use the global defined transport if no one is defined.
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
            async_send_specific_failure_queue:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                failed_transport: failed_important # takes precedence over the global defined "failed_transport"
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2

You can test this feature easily on a demo project. Just follow the README.

More information on issue #34911

What needs to be done so this can be merged:

  • validate strategy
  • update src/**/CHANGELOG.md files
  • update tests to cover all cases
  • create doc PR

@nicolas-grekas nicolas-grekas added this to the next milestone Dec 15, 2019
@monteiro monteiro force-pushed the messenger-multiple-failed-transports branch from 9a7eb40 to 099a646 Compare December 27, 2019 13:51
@monteiro monteiro changed the base branch from 4.4 to master December 27, 2019 13:51
@monteiro monteiro force-pushed the messenger-multiple-failed-transports branch from 93c5188 to f995ffa Compare December 27, 2019 17:48
@monteiro monteiro changed the title messenger: multiple failed transports support [Messenger] multiple failed transports support Jan 2, 2020
@monteiro
Copy link
Contributor Author

monteiro commented Jan 20, 2020

@sroze thanks a lot for the review.

@monteiro
Copy link
Contributor Author

monteiro commented Mar 25, 2020

Can someone help me with the review?

I was very careful in order to not break any command or any class by extending some classes with more constructor parameters or adding more options to commands (the failed commands).

I probably need more tests on the FrameworkBundle (extension part).

@giovannialbero1992
Copy link
Contributor

What is missing in this PR? Only Changelog updates?

@monteiro monteiro force-pushed the messenger-multiple-failed-transports branch from 252c1ef to 7b4481a Compare September 20, 2020 15:30
@monteiro
Copy link
Contributor Author

monteiro commented Sep 20, 2020

@fabpot @weaverryan This PR is ready to be reviewed.

I created a symfony project, so anyone can test with my latest changes.

@monteiro monteiro requested a review from weaverryan September 20, 2020 16:01
@monteiro monteiro changed the title [Messenger] multiple failed transports support [Messenger] multiple failure transports support Sep 21, 2020
$this->assertSame('messenger.transport.'.$expectedFailureTransportsMapping[$transportName], (string) $ref, sprintf('The transport "%s" does not have the expected failed transport reference', $transportName));
}

$failedMessageTransportListenerReference =
Copy link
Member

Choose a reason for hiding this comment

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

Nit pick - this is really a Definition, not a Reference

$container->getDefinition('console.command.messenger_failed_messages_show')
->replaceArgument(0, $config['failure_transport']);
->replaceArgument(0, $globalFailureReceiver)
->replaceArgument(1, $container->getDefinition($failureTransportsServiceLocatorId));
Copy link
Member

Choose a reason for hiding this comment

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

these should be references, right? We don't normally set a Definition directly on an argument. And should it use $failureTransportsServiceLocatorId or $failureTransportsServiceLocator - I'm getting lost (this stuff confuses me) in which is which and what should be used.

->replaceArgument(0, $config['failure_transport']);
->replaceArgument(0, $globalFailureReceiver)
->replaceArgument(1, $senderReferences[$config['failure_transport']] ?? null)
->replaceArgument(5, $container->getDefinition($failureTransportsServiceLocator));
Copy link
Member

Choose a reason for hiding this comment

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

Same problem here (ish) as below - we should pass a Reference (which $failureTransportsServiceLocator is), not a Definition.

$container->removeDefinition('console.command.messenger_failed_messages_retry');
$container->removeDefinition('console.command.messenger_failed_messages_show');
$container->removeDefinition('console.command.messenger_failed_messages_remove');
$container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener');
Copy link
Member

Choose a reason for hiding this comment

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

we could revert this change to lessen the diff

if ($transport['failure_transport']) {
if (!isset($config['transports'][$transport['failure_transport']])) {
throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $transport['failure_transport']));
}
Copy link
Member

Choose a reason for hiding this comment

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

Should we be checking for !isset($senderReferences[$transport['failure_transport']]) here instead? That would be consistent with the above code. It's more confusing, but there is the edge case that the failure_transport is not a registered transport, but just a "sender service id"

abstract_arg('failed transports map by transport name'),
])
->tag('container.service_locator')

Copy link
Member

Choose a reason for hiding this comment

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

If I'm correct in FrameworkExtension, these won't be needed.

$definition = $container->getDefinition($failedCommandId);
$definition->replaceArgument(1, $receiverMapping[$definition->getArgument(0)]);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, wait a second. This is subtle. But currently, in FrameworkExtension, you are populating arg 0 with $senderReferences[$config['failure_transport']]. But before, we were populating them with receiver references. Even for me, this makes my head spin a bit :P. You could technically have a failure transport "receiver" service but no "sender" service... though this is (at the very least) an edge case. Usually a sender & receiver are a "pair" because you've defined them as a transport. But this doesn't need to be the case.

The situation would be this: I create a ReceiverInterface service and tag it with messenger.receiver and alias my_failure_receiver. But, I do not actually register this as a transport. Then, at the command line, I use messenger:failure:show --failure-transport=my_failure_receiver. That is technically legal. And it's even possible (though unlikely) to set up this kind of situation if you're using AMQP (e.g. you actually set up a true failure_transport called failure1, but then with custom binding & routing rules, those messages ultimately end up in a queue connected with the custom my_failure_receiver receiver.

So.... that's why this code was originally here and I think it probably still needs to be. I don't think that's a problem, other than some of the new code in FrameworkExtension should actually live here.

@fabpot fabpot closed this Oct 7, 2020
@fabpot
Copy link
Member

fabpot commented Oct 7, 2020

We've just moved away from master as the main branch to use 5.x instead. Unfortunately, I cannot reopen the PR and change the target branch to 5.x. Can you open a new PR referencing this one to not loose the discussion? Thank you for your understanding and for your help.

@monteiro
Copy link
Contributor Author

monteiro commented Oct 7, 2020

opened new PR #38468 with 5.x as base

@luigi370
Copy link

This is just merged into symfony 5 right? is there any change to make it work on symfony 4.4 without updating the framework? thanks!

@stof
Copy link
Member

stof commented Jun 15, 2022

updating the framework...
Old versions of Symfony don't get the new features of newer versions (that would defeat the purpose of versioning)

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.

[Messenger][Feature] Failed queue per transport
9 participants