Consider the following small program that simply creates a TransactionScope
, prints Transaction.Current
, calls a method in another AppDomain (that takes a while to execute) and then prints Transaction.Current
upon return.
using System;
using System.Linq;
using System.Runtime.Remoting.Lifetime;
using System.Threading;
using System.Transactions;
namespace TransactionScopeFlowTest
{
class Program
{
static void Main(string[] args)
{
// These times are just to generate the error faster. Normally the initial lease is 5 minutes, meaning the method call
// would have to take 5 minutes to occur, so we speed it up here for demonstration purposes.
LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(1);
LifetimeServices.LeaseTime = TimeSpan.FromSeconds(1);
LifetimeServices.RenewOnCallTime = TimeSpan.FromSeconds(1);
AppDomain domain = AppDomain.CreateDomain("Temp", null, AppDomain.CurrentDomain.SetupInformation);
using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Console.WriteLine($"Transaction Before Call = {Transaction.Current?.TransactionInformation?.LocalIdentifier?.ToString() ?? "<null>"}");
domain.DoCallBack(AppDomainCallback);
Console.WriteLine($"Transaction After Call = {Transaction.Current?.TransactionInformation?.LocalIdentifier?.ToString() ?? "<null>"}");
scope.Complete();
}
AppDomain.Unload(domain);
}
public static void AppDomainCallback()
{
Thread.Sleep(3000);
}
}
}
Quite unexpectedly, the program generates the following output:
Transaction Before Call = 1f980219-2583-4796-8d6d-256a6f100698:1
Transaction After Call = <null>
If I change the TransactionScopeAsyncFlowOption
in the ctor of TransactionScope
to TransactionScopeAsyncFlowOption.Suppress
, then the Transaction remains after the call.
My suspicion is that the transaction scope flow across the logical context is handled by CallContext which is propagated across the remoting call, and the keys used inherits MarshalByRefObject
and since they are not registered with any ISponsor
, the proxy will disconnect after the initial lease time. And then the logical call context is merged back with the original once we return from the call, meaning that the transaction no longer exists.
I'm looking for a way to circumvent this problem, and also if this is to be considered a bug in .NET?