4

Using second-level retries in Rebus (https://github.com/rebus-org/Rebus/wiki/Automatic-retries-and-error-handling) I need to forward a message to an error queue after n retries.

This works:

_bus.Advanced.Routing.Send("my-queue.error",failedMessage.Message, failedMessage.Message);

But the exceptions accumulated in the failed message is not brought along, making the failed message in the error queue rather useless.

Ideally I would hook into the ITransport instance, and do something like this

await _transport.Send(errorQueueAddress, transportMessage, transactionContext);

(from PoisonQueueErrorHandler: https://github.com/rebus-org/Rebus/blob/333dbedf486acb92bd6c6250755537032c6215fd/Rebus/Retry/PoisonQueues/PoisonQueueErrorHandler.cs)

But there is no apparent way to get to that instance.

Any ideas on how to achieve this?

Christian Dalager
  • 6,603
  • 4
  • 21
  • 27

1 Answers1

3

One way of forwarding the message to the error queue from your second level handler, is to simply throw exceptions – the usual n retries applies to 2nd level retries too, so if everything keeps failing, the message will eventually end up in the error queue.

If you're using Rebus version 6.2.1 or later, there's an extension method on ITransportMessageApi that enables you do manually dead-letter the message when you feel like it like this:

await bus.Advanced.TransportMessage.Deadletter("whatever error details you'd like to include!");

If you're on Rebus 6.7.0 or later, the Deadletter method has an overload that accepts an exception too, which lends itself nicely to do stuff like this:

try
{
    // try something that can fail
}
catch (Exception exception)
{
    await bus.Advanced.TransportMessage.Deadletter(exception);
}

The following answer is relevant for Rebus versions BELOW 6.2.1:

If you really want to emulate Rebus' behavior when forwarding the message to the error queue, you can get the full error details as they would have been included by Rebus via the ErrorDescription property on IFailed<YourMessage>.

You can then create these headers when forwarding the message:

var details = failedMessage.ErrorDescription;

var headers = new Dictionary<string, string>
{
    {Headers.SourceQueue, "your-input-queue"},
    {Headers.ErrorDetails, details}
};

and then you should forward the transport message to the error queue, thus preserving message identity and all other headers completely intact:

await bus.Advanced.TransportMessage.Forward("error", headers);

I think this would be the most realistic emulation of Rebus' behavior.

mookid8000
  • 18,258
  • 2
  • 39
  • 63
  • 1
    I'm having similar needs implementing a second level handler. Is the above recommendation from 2017 on throwing exceptions inside the IFailed handler (n times equal to `SimpleRetryStrategy`'s configured `maxDeliveryAttempts`) still the way to do it? Having a look at the source code, I was not able to find a better solution. – dhrm Feb 24 '20 at 18:32
  • Yes... although I would probably look at the sketched solution on how to emulate Rebus' "forward to dead-letter queue" behavior, to reduce the amount of unnecessary noise which goes into your logs – mookid8000 Feb 24 '20 at 18:37
  • @mookid8000 Any way to reach TransportMessage from SyncBus? – acidduck Jun 09 '20 at 08:08
  • The "transport message API"? Unfortunately, no. `ISyncBus` only has all of the *methods* from `IBus`, so the `IAdvancedApi` returned from the `Advanced` property is not mirrored there. What do you need to do? – mookid8000 Jun 09 '20 at 08:50
  • I want to move an IFailed message to the error queue, but I guess I can just throw an exception as you purposed. The forwarding way just seemed nicer :-) – acidduck Jun 09 '20 at 09:34
  • How to dynamically get the error queue name? – Coastpear Feb 13 '23 at 11:57
  • @Coastpear that's not necessary I'll just update the answer to show you why – mookid8000 Feb 20 '23 at 19:13