5

When reassigning Transaction.Current, I seem to lose the original TransactionScopeAsyncFlowOption behavior of my TransactionScope. The code after the second await loses it's Transaction.Current value.

Sample code (Linqpad):

async Task Main()
{
    Thread.CurrentThread.ManagedThreadId.Dump("before await");
    var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled);
    var transaction = Transaction.Current;

    await Task.Delay(1000).ConfigureAwait(false);

    Thread.CurrentThread.ManagedThreadId.Dump("after first await");
    Transaction.Current = transaction;
    Transaction.Current.Dump(); // not null

    await Task.Delay(1000).ConfigureAwait(false);

    Thread.CurrentThread.ManagedThreadId.Dump("after second await");
    Transaction.Current.Dump(); // is null :/
}

I know I should be using a using statement on TransactionScope rather than reassigning the ambient transaction, however because of reasons, it's not possible to do that. I'm curious about the reason of the behavior of the snippet above and wonder if there are ways to keep the original TransactionScopeAsyncFlowOption behavior.

ThomasDC
  • 484
  • 3
  • 11
  • Where are you running that code? I tried LINQPad and, if I don't set `Transaction.Current` I allways get the same transaction. – Paulo Morgado May 02 '19 at 17:30
  • 4
    Looks like the implementation of the setter for `Transaction.Current` always uses TLS context, never async-compatbile context. The implementation of the `TransactionScope` constructor (specifically, `PushScope`) does set either one. Looks like you'll have to open a bug report. – Stephen Cleary May 02 '19 at 20:00
  • 1
    The associated bug report in dotnet/corefx can be found [here](https://github.com/dotnet/corefx/issues/37402). – ThomasDC May 03 '19 at 13:28

1 Answers1

1

I managed to fix the issue by wrapping the Transaction object in another TransactionScope with TransactionScopeAsyncFlowOption enabled:

async Task Main()
{
    var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled);
    var transaction = Transaction.Current;

    await Task.Delay(1000);

    Transaction.Current = transaction;
    Debug.Assert(Transaction.Current != null); // not null

    await Task.Delay(1000);

    Debug.Assert(Transaction.Current == null); // is null :/

    using (var innerScope = new TransactionScope(transaction, TransactionScopeAsyncFlowOption.Enabled))
    {
        // Transaction.Current state is kept across async continuations
        Debug.Assert(Transaction.Current != null); // not null
        await Task.Delay(10);
        Debug.Assert(Transaction.Current != null); // not null
        innerScope.Complete();
    }
}
ThomasDC
  • 484
  • 3
  • 11
  • 1
    Brilliant! And you've submitted your fix in the form of a pull request, which I've accepted and published as Rebus.TransactionScopes 5.0.0-b2 – thanks! – mookid8000 May 06 '19 at 08:43