rebus-org / Rebus

:bus: Simple and lean service bus implementation for .NET
https://mookid.dk/category/rebus
Other
2.31k stars 361 forks source link

AsyncLocal<ITransactionContext> in AmbientTransactionContext.Current #843

Closed kewinbrand closed 4 years ago

kewinbrand commented 4 years ago

Hey,

I have some custom logic that automatically creates a RebusTransactionScope in a middleware (ASP Net Core), but sometimes the AsyncLocal (the one in AmbientTransactionContext) does not persists its value after the controller action is executed (changes to null). In some cases it is correctly set by the scope and in other not. Basically what the middleware does is:

public async Task Invoke(HttpContext httpContext)
{
  //MessageContext.Current is null
  using (var scope = new RebusTransactionScope())
  {
       //MessageContext.Current is not null

        await next(httpContext);

       //MessageContext.Current is null

       await scope.CompleteAsync();
  }
}

What the controller action does is send a message with the IBus.

Maybe this is not purely related to Rebus but could you shed me some light?

Cheers

kewinbrand commented 4 years ago

After digging a bit I found that (well this is at least what I understood) AsyncLocal does not flow from inner context to outer context. My RebusTransactionScope creation is actually async (as I also need to open a db connection) like:

public async Task Invoke(HttpContext httpContext)
{
  //MessageContext.Current is null
  using(var tr = await CustomBeginTransactionAsync())
  {  
       //MessageContext.Current is null

        await next(httpContext);

       //MessageContext.Current is null

       await scope.CompleteAsync();
  }
}

Any thoughts?

mookid8000 commented 4 years ago

I know that AsyncLocal does not flow from inner to outer, but RebusTransactionScope – by virtue of being a "scope" – is meant to be used in a way that surround the code to be affected by it.

The transaction context then stores a couple of things, e.g. a concurrent dictionary of items – but since the dictionary gets copied by reference, inner code will be working on the same dictionary instance.

Could you maybe show me some more about what is going on in your code?

mookid8000 commented 4 years ago

Hi @kewinbrand , have you figured it out?

Just want to tell you that RebusTransactionScope is not supposed to create a "message context", because a "message context" is what you have in Rebus when you're handling a message.

That is, MessageContext.Current will only be not-null, when you're

public class SomeMessageHandler : IHandleMessages<SomeMessage>
{
    public async Task Handle(SomeMessage message)
    {
        //< here! :)
    }
}

I'll close this issue for now – feel free to come back, if there's still something puzzling you. 🙂