Search code examples
rebus

Publishing the Rebus mesage from exception catch block without hampering retry mechanism


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


Solution

  • 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.