[Messenger] Add ChainStamp to dispatch messages one after another - #65903
Open
nicolas-grekas wants to merge 1 commit into
Open
[Messenger] Add ChainStamp to dispatch messages one after another#65903nicolas-grekas wants to merge 1 commit into
nicolas-grekas wants to merge 1 commit into
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
alexandre-daubois
left a comment
Member
There was a problem hiding this comment.
That would be nice, I came across a lot of situations in highly async env where that would have been really useful
nicolas-grekas
force-pushed
the
messenger-chain
branch
3 times, most recently
from
September 13, 2026 07:25
ae8a981 to
d794296
Compare
A ChainStamp lists the messages to handle after the one it is attached to, in order. The new ChainMiddleware dispatches the next message of the chain once the current one is handled, with a DispatchAfterCurrentBusStamp, and passes the remaining messages along in a new ChainStamp. The next message keeps the bus name of the current one unless it names its own bus. When a step fails, the rest of the chain is not dispatched. The remaining steps travel with a message sent to a transport, so the chain continues from there once a failed step is retried. The Messenger bundle registers the middleware as messenger.middleware.chain, bound to the routable message bus, and adds it to the default middleware stack between send_message and handle_message.
nicolas-grekas
force-pushed
the
messenger-chain
branch
from
September 13, 2026 13:57
d794296 to
4e185ae
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a proposal, open for discussion. It started from Dariusz Gafka's article Symfony Messenger vs Ecotone: The Real Difference, which describes how Ecotone approaches this. What is proposed here is a free interpretation for Symfony, built on Messenger's own model rather than ported from Ecotone, so it departs from the article where the two models differ.
A multi-step flow is usually built by having each handler dispatch the next message class. The sequence is then implied by dispatch calls scattered across handlers, and nothing survives a retry. This PR lets the sequence be declared once, at dispatch time:
Each message of the chain is dispatched once the previous one was handled by all its handlers. A failing step stops the chain. The remaining steps travel in the stamp, so a step that is retried (retry strategy or
messenger:failed:retry) resumes the chain from where it stopped.Public API
Symfony\Component\Messenger\Stamp\ChainStamp:__construct(object ...$messages)(messages orEnvelopeinstances, in handling order; an empty chain throws),getMessages(): array.Symfony\Component\Messenger\Middleware\ChainMiddleware: dispatches the next message once the current one is handled. It runs betweensend_messageandhandle_message, so a message sent to a transport starts its next step in the worker that handles it, not at send time.messenger.middleware.chain(shared by all buses, bound to the routable bus) and addschainto the default middleware stack betweensend_messageandhandle_message. Buses configured withdefault_middleware: falsehave to list it themselves.Behavior
DispatchAfterCurrentBusStamp: it runs after the current message completed, after the commit of adoctrine_transactionmiddleware, and in a worker after the current handlers returned.dispatch()call.Envelope, to give that step its own stamps (DelayStamp,TransportNamesStamp,BusNameStamp, ...). AnEnvelopeitem carrying its ownChainStampruns that inner chain before the remaining outer steps. Its non-sendable stamps are dropped, since the chain travels inside another envelope, which would otherwise carry anAckStampclosure or aReceivedStampof its own into the next send.BusNameStamp.HandlerFailedExceptionfromdispatch(); a failing later synchronous step surfaces asDelayedMessageHandlingException. In a worker, a synchronous step failing right after an asynchronous one fails that asynchronous message as a whole, so steps that follow an asynchronous one should be idempotent, or be routed to a transport so that each step is retried on its own.chainsits in the middleware stack.PhpSerializerhandles it; transports using the Symfony serializer need normalizers for the chained messages, as they do forRedispatchMessage.Checks
./phpunit src/Symfony/Component/Messenger/Testsand./phpunit src/Symfony/Bundle/FrameworkBundle/Tests: green (skips are the usual missing-server ones).MessengerBundlerather than FrameworkBundle, following the split upstream, so the version guards the branch carried are gone.Documentation
DispatchAfterCurrentBusStamp.