0
votes

I am trying to publish a message from the catch block in the Rebus message handlers without affecting the retry mechanism of rebus.

My intent is,

  • Process the message in message handlers.
  • In case of an error, catch it, and publish some error event using the same bus.
  • Just after publishing "throw" the caught exception so that rebus ACK/NACK is placed automatically for the retry/re-delivery of the message.

I cannot achieve the above because if an exception is thrown from the rebus message handlers, that message is automatically flagged for re-delivery and the whole pipeline transaction is rolled back. This negates the second point above as when transaction is rolled back, the message I sent to be published is rolled back as well. Is there a way I could achieve this i.e. publishing the message as well as auto-retry ability. My message handler code is below.

public Task Handle(SomeMessage message)
    {
        try
        {
            //Some code that may result in an error
        }
        catch (Exception ex)
        {
            _bus.PublishSomeMessageErrorEvent(ex);

            // throw an error and let Rebus retry the delivery.
            throw;
        }
        return Task.CompletedTask;
    }

I Also tried working with second level retries so that when failed message comes in the IHandleMessages<IFailed> handler I would simply publish the message with _bus.PublishSomeMessageErrorEvent(...) method but while setting the second level retry I received exception while starting the bus.

_bus = Configure.With(...)
      .Options(r=>r.SimpleRetryStrategy(secondLevelRetriesEnabled:true))
      .ConfigureSqlServerTransportFromAppConfig()
      .Logging(c => c.Log4Net())
      .Start();

The exception Attempted to register primary -> Rebus.Retry.Simple.SimpleRetryStrategySettings, but a primary registration already exists: primary -> Rebus.Retry.Simple.SimpleRetryStrategySettings

1

1 Answers

1
votes

You can use Rebus' built-in "transaction scope suppressor" by creating a scope with it like this:

using (new RebusTransactionScopeSuppressor())
{
    // bus operations in here will not be enlisted
    // in the transaction scope of the message
    // context (i.e. the one associated with the
    // handler)
}

so your message handler can simply go

public class SomeMessageHandler : IHandleMessages<SomeMessage>
{
    readonly IBus _bus;

    public SomeMessageHandler(IBus bus) => _bus = bus ?? throw new ArgumentNullException(nameof(bus));

    public async Task Handle(SomeMessage message)
    {
        try
        {
            await DoSomethingThatCanThrow();
        }
        catch (Exception exception)
        {
            // publish event
            using (new RebusTransactionScopeSuppressor())
            {
                var evt = new CaughtThisException(exception.ToString());

                await _bus.Publish(evt);
            }

            // rethrow to have Rebus exception handling kick in
            throw;
        }
    }

    async Task DoSomethingThatCanThrow()
    {
        // (...)
    }
}

and achieve what you want.


PS: Remember to await async things 🙂 it's not clear whether the PublishSomeMessageErrorEvent is sync or async, but somehow your code looks a bit like it could actually be async.