Retries over Symfony Messenger#
When activities run over Symfony Messenger, two systems could retry a failed activity. This page explains how the work is split between them, and why an activity failure has only one retry counter. An activity is a unit of side effect in a workflow, such as an HTTP call, a database write or an e-mail. The journal is the append-only record of everything an execution decided and received. See the glossary.
Which system owns each retry setting#
Durable decides whether and when an activity is retried. Messenger delivers the messages.
| Question | Decided by | Setting |
|---|---|---|
| Is this failure worth another attempt? | Durable | nonRetryableExceptions on ActivityOptions |
| How many attempts, and how long between them? | Durable | retryLimit, initialInterval, backoffCoefficient, maximumInterval; max_activity_retries caps them all, except on Temporal |
| Where does a failed activity go for an operator to look at? | Messenger | failure_transport |
| Delivery, acknowledgement, the transport itself | Messenger | the durable_activities transport |
One retry counter#
Durable schedules its own retries. A retried attempt is a new message, queued with its delay. The
handler does not throw for a failure that Durable retries, so Messenger’s retry_strategy does not
apply to activity failures, and there is no second counter to keep in step with the first.
A non-retryable failure takes another path. Durable writes it to the journal, then throws it as
UnrecoverableMessageHandlingException. Messenger does not retry it, and sends it to
failure_transport if you configured one. Running messenger:failed:retry on that message runs
nothing again, because the attempt is already in the journal.
Two messages that bypass max_retries#
Two kinds of message bypass max_retries on purpose. Each one waits for something that another
worker settles.
An activity attempt that another worker is running. On a DBAL journal, Durable claims each
attempt before running it. Laravel does the same; an in-memory journal runs in one process and
claims nothing. A copy that finds the claim taken is retried later, on the transport’s
retry_strategy. It then finds the attempt in the journal or, if the worker holding the claim
died, runs the attempt once the claim expires.
A resume that arrives before the fact it announces. A worker sends one resume before it
journals what the resume announces (an activity’s outcome, a signal, a child’s outcome, a fired
timer), and another one after. The early resume fails on purpose and is retried. After ten
redeliveries it is acknowledged, and the messenger channel logs an info line:
“dropped an early resume of execution … the resume sent after its append carries the run”. The
run is not lost, and nothing reaches failure_transport.