Investigating & Retrying Failed Messages

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Apparently now that we've configured a failure_transport, if handling a message still isn't working after 3 retries, instead of being sent to /dev/null, they're sent to another transport - in our case called "failed". That transport is... really... the same as any other transport... and we could use the messenger:consume command to try to process those messages again.

But, there's a better way. Run:

php bin/console messenger

Seeing Messages on the Failed Queue

Hey! Shiny new commands are hiding here! Three under messenger:failed. Try out that messenger:failed:show one:

php bin/console messenger:failed:show

Nice! There are our 4 failed messages... just sitting there wait for us to look at them. Let's pretend that we're not sure what went wrong with these messages and want to check them out. Start by passing the 115 id:

php bin/console messenger:failed:show 115

I love this: it shows us the error message, error class and a history of the message's misadventures through our system! It failed, was redelivered to the async transport at 05, at 06 and then at 07, it finally failed and was redelivered to the failed transport.

If we add a -vv on the command...

php bin/console messenger:failed:show 115 -vv

Now we can see a full stack trace of what happened on that exception.

This is a really powerful way to figure out what went wrong and what to do next: do we have a bug in our app that we need to fix before retrying this? Or maybe it was a temporary failure and we can try again now? Or maybe, for some reason, we just want to remove this message entirely.

If you did want to remove this without retrying, that's the messenger:failed:remove command.

Retrying Failed Messages

But... let's retry this! Back in the handler, change this back to fail randomly.

... lines 1 - 13
class AddPonkaToImageHandler implements MessageHandlerInterface, LoggerAwareInterface
{
... lines 16 - 30
public function __invoke(AddPonkaToImage $addPonkaToImage)
{
... lines 33 - 46
if (rand(0, 10) < 7) {
throw new \Exception('I failed randomly!!!!');
}
... lines 50 - 56
}
}

There are two ways to work with the retry command: you can retry a specific id like you see here or you can retry the messages one-by-one. Let's do that. Run:

php bin/console messenger:failed:retry

This is kind of similar to how messenger:consume works, except that it asks you before trying each message and, instead of running this command all the time on production, you'll run it manually whenever you have some failed messages that you need to process.

Cool! We see the details and it asks if we want to retry this. Like with show, you can pass -vv to see the full message details. Say "yes". It processes... and then continues to the next. Actually, let me try that again with -vv so we can see what's going on:

php bin/console messenger:failed:retry -vv

When Failed Messages... Fail Again

This time we see all the details. Say "yes" again and... nice: "Received message", "Message handled" and onto the next message. We're on a roll! Notice that this message's id is 117 - that'll be important in a second. Hit yes to retry this message too.

Woh! This time it failed again! What does that mean? Well remember, the failure transport is really just a normal transport that we're using in a special way. And so, when a message fails here, Messenger... retries it! Yea it was sent back to the failure transport!

I'll hit Control+C and re-run the show command:

php bin/console messenger:failed:show

That id 119 was not there when we started. Nope, when message 117 was processed, it failed, was redelivered to the failure transport as id 119, and then was removed. And so, unless you change your configuration, messages will be retried 3 times on the failure transport before finally being completely discarded.

Oh, but if you look at the retried message closer:

php bin/console messenger:failed:show 119 -vv

There's a bit of a bug: the error and error class are missing. The data is still in the database... it's just not displayed correctly here. But you can see the message's history: including that it was sent to the failed transport and then sent again to the failed transport.

By the way, you can pass a --force option to the retry command if you want it to retry messages one-by-one without asking you each time whether or not it should do it. Also, not all the transport types - like AMQP or Redis - support all of the features we just saw if you use it as your failure transport. That may change in the future, but at this moment - Doctrine is the most robust transport to use for failures.

Anyways, as cool as failing is, let's go back and remove the code that's breaking our handler. Because... it's time to take a step deeper into how Messenger works: it's time to talk about middleware.

Leave a comment!

This tutorial is built with Symfony 4.3, but will work well on Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.1", // v1.9.6
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.7
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/test-pack": "^1.0" // v1.0.6
    }
}