Skip to content

Commit

Permalink
Retry distributed transaction tests on MSDTC startup failure (#73941)
Browse files Browse the repository at this point in the history
Fixes #73874
  • Loading branch information
roji authored Aug 16, 2022
1 parent 66eeb2d commit c7b500d
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,6 @@
<value>[Base]</value>
</data>
<data name="DistributedNotSupportOn32Bits" xml:space="preserve">
<value>Distributed transactions are currently unsupported on 32-bit version of the .NET runtime.</value>
<value>Distributed transactions are currently unsupported in 32-bit processes.</value>
</data>
</root>
86 changes: 66 additions & 20 deletions src/libraries/System.Transactions.Local/tests/OleTxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,26 @@ namespace System.Transactions.Tests;
#nullable enable

[PlatformSpecific(TestPlatforms.Windows)]
public class OleTxTests
public class OleTxTests : IClassFixture<OleTxTests.OleTxFixture>
{
//private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(1);

public OleTxTests(OleTxFixture fixture)
{
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
[InlineData(Phase1Vote.Prepared, Phase1Vote.Prepared, EnlistmentOutcome.Committed, EnlistmentOutcome.Committed, TransactionStatus.Committed)]
[InlineData(Phase1Vote.Prepared, Phase1Vote.ForceRollback, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)]
[InlineData(Phase1Vote.ForceRollback, Phase1Vote.Prepared, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)]
public void Two_durable_enlistments_commit(Phase1Vote vote1, Phase1Vote vote2, EnlistmentOutcome expectedOutcome1, EnlistmentOutcome expectedOutcome2, TransactionStatus expectedTxStatus)
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

try
{
Expand Down Expand Up @@ -57,12 +60,12 @@ public void Two_durable_enlistments_commit(Phase1Vote vote1, Phase1Vote vote2, E
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
public void Two_durable_enlistments_rollback()
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted);
var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted);
Expand All @@ -85,12 +88,12 @@ public void Two_durable_enlistments_rollback()
[InlineData(2)]
public void Volatile_and_durable_enlistments(int volatileCount)
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

if (volatileCount > 0)
{
Expand Down Expand Up @@ -118,7 +121,7 @@ public void Volatile_and_durable_enlistments(int volatileCount)
[ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))]
public void Promotion()
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}
Expand All @@ -131,7 +134,7 @@ public void Promotion()
// 3. The main process will then notify the 1st external process to commit (as the main's transaction is delegated to it).
// 4. At that point the MSDTC Commit will be triggered; enlistments on both the 1st and 2nd processes will be notified
// to commit, and the transaction status will reflect the committed status in the main process.
var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

string propagationTokenFilePath = Path.GetTempFileName();
string exportCookieFilePath = Path.GetTempFileName();
Expand Down Expand Up @@ -212,7 +215,7 @@ public void Promotion()

static void Remote1(string propagationTokenFilePath)
{
var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

var outcomeEvent = new AutoResetEvent(false);
var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent);
Expand Down Expand Up @@ -244,7 +247,7 @@ static void Remote2(string exportCookieFilePath)
{
// Load the export cookie and enlist durably
byte[] exportCookie = File.ReadAllBytes(exportCookieFilePath);
var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie);
using var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie);

// Now enlist durably. This triggers promotion of the first PSPE, reading the propagation token.
var outcomeEvent = new AutoResetEvent(false);
Expand Down Expand Up @@ -297,15 +300,15 @@ public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
[ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))]
public void Recovery()
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

// We are going to spin up an external process to also enlist in the transaction, and then to crash when it
// receives the commit notification. We will then initiate the recovery flow.

var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

var outcomeEvent1 = new AutoResetEvent(false);
var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent1);
Expand Down Expand Up @@ -367,7 +370,7 @@ public void Recovery()
static void EnlistAndCrash(string propagationTokenText, string resourceManagerIdentifierGuid, string recoveryInformationFilePath)
{
byte[] propagationToken = Convert.FromBase64String(propagationTokenText);
var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken);
using var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken);

var crashingEnlistment = new CrashingEnlistment(recoveryInformationFilePath);
tx.EnlistDurable(Guid.Parse(resourceManagerIdentifierGuid), crashingEnlistment, EnlistmentOptions.None);
Expand Down Expand Up @@ -409,12 +412,12 @@ public void InDoubt(Enlistment enlistment)
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
public void TransmitterPropagationToken()
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier);

Expand All @@ -430,12 +433,12 @@ public void TransmitterPropagationToken()
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
public void GetExportCookie()
{
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

var tx = new CommittableTransaction();
using var tx = new CommittableTransaction();

var whereabouts = TransactionInterop.GetWhereabouts();

Expand Down Expand Up @@ -474,4 +477,47 @@ private static void Retry(Action action)
}
}
}

public class OleTxFixture
{
public OleTxFixture()
{
if (!Environment.Is64BitProcess)
{
return; // Temporarily skip on 32-bit where we have an issue
}

// In CI, we sometimes get XACT_E_TMNOTAVAILABLE on the very first attempt to connect to MSDTC;
// this is likely due to on-demand slow startup of MSDTC. Perform pre-test connecting with retry
// to ensure that MSDTC is properly up when the first test runs.
int nRetries = 5;

while (true)
{
try
{
using var tx = new CommittableTransaction();

var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed);
var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed);

tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None);
tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None);

tx.Commit();

return;
}
catch (TransactionException e) when (e.InnerException is TransactionManagerCommunicationException)
{
if (--nRetries == 0)
{
throw;
}

Thread.Sleep(100);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,4 @@
<Compile Include="TestEnlistments.cs" />
<Compile Include="TransactionTracingEventListener.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\System.Configuration.ConfigurationManager\src\System.Configuration.ConfigurationManager.csproj" />
</ItemGroup>
</Project>

0 comments on commit c7b500d

Please sign in to comment.