0

We use NServiceBus 6 with transports MSMQ and RabbitMq. When we had MSMQ with distributed transactions, we never had message dispatched earlier than distributed transaction get committed. However when we turned it off and wrap it inside transaction scope manually we see that Nsb started sending messages before the handler execution is over. Our code looks like this

public Task Handle(ICommand1 message, IMessageHandlerContext context)
{
   using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
   {    
       HandleMessage1(message1);

       _session.Flush();
       tx.Commit();
    }   
}

private void HandleMessage1(ICommand1 message1)
{
    // Updating database
    ...
    // Sending other Command2 to separate handler
    bus.Send<ICommand2>(x =>
    {
       ...
    });
}

From logs I can see that ICommand2 starts handling earlier than ICommand1 handler managed to commit data changes in database, getting 'old' data, before Command1 handler get them updated.

I was under impression that we won't have this sort of problem since Nsb6 provides us with Batched message dispatch. Looks like it's not a case for us, I wonder why and how we can fix that. I tried to run it under MSMQ (no distrib. transaction) and RabbitMq, result is the same.

Command2 handler works on changes made by Command1 handler, so how can we make them work sequentially?

YMC
  • 4,925
  • 7
  • 53
  • 83

1 Answers1

2

Sending messages from the handler should be done using the context passed into handler. When you use bus.Send() you're likely using IMessageSession rather than IMessageHandlerContext, which is equivalent to immediate dispatch.

Changing your code to the following should resolve it:

public async Task Handle(ICommand1 message, IMessageHandlerContext context)
{
   using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
   {    
       await HandleMessage1(message1, context);

       _session.Flush();
       tx.Commit();
    }   
}

private async Task  HandleMessage1(ICommand1 message1, IMessageHandlerContext context)
{
    // Updating database
    ...
    // Sending other Command2 to separate handler
    await context.Send<ICommand2>(x =>
    {
       ...
    });
}

Sean Feldman
  • 23,443
  • 7
  • 55
  • 80
  • this "bus" variable is actually wraps IMessageHandlerContext reference, so in the end it calls IMessageHandlerContext.IPipelineContext.Send, we do not use ISendOnlyEndpoint – YMC Mar 22 '19 at 00:50
  • Ok. Make sure it's used with `await` as in the code I've showed. – Sean Feldman Mar 22 '19 at 00:52
  • inside it actually runs context.Send(x => ...).GetAwaiter().GetResult(). could it cause the problem? – YMC Mar 22 '19 at 01:10
  • Inside what? You don't need a field, can flow the context and don't do `GetAwaiter().GetResult()`. – Sean Feldman Mar 22 '19 at 01:12
  • inside the "bus" wrapper. This wrapper exists to cover different call contexts, like web or legacy code, which is not on async framework, and not inside Nsb handler. I made the sample code simplistic, but HandleMessage1 is actually method of the service that can be run from varies contexts, not inside Nsb handler only. That's why we wrap it in our own "Bus" that can run from whatever. However for this particular case it picks up IMessageHandlerContext which we store on pipeline initialization. – YMC Mar 22 '19 at 01:30
  • You shouldn't use IMessageSession where IMessageHandlerContext should be used. Have a look at IUniformSession to help with this issue: https://docs.particular.net/nservicebus/messaging/uniformsession – Sean Feldman Mar 22 '19 at 01:44
  • No, they are used properly, IMessageSession is not used instead of IMessageHandlerContext. "Bus" is smart enough to understand host under which it's working. I'm just explaining why I can't pass IMessageHandlerContext inside like in your example. I'll try to run "await" instead of .GetAwaiter().GetResult() tomorrow and let you know if it helps – YMC Mar 22 '19 at 04:41
  • I updated it to async/await like in your example, but it keeps dispatching messages immediately on Send() call – YMC Mar 22 '19 at 21:53
  • Could you share a reproduction on GitHub (or similar)? If not, support is where I'd send this question. – Sean Feldman Mar 22 '19 at 22:07
  • it's commercial code, unfortunately I'm not allowed to share it. Thank you anyway. At least now I know that I have not overlooked anything in Nsb documentation. There must be just something wrong in our own "Bus" implementation. I'll keep investigating – YMC Mar 23 '19 at 02:12