1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Diagnostics; 6 using System.Reflection; 7 using System.Threading; 8 using System.Threading.Tasks; 9 using Xunit; 10 11 namespace System.Transactions.Tests 12 { 13 public class LTMEnlistmentTests 14 { 15 const int MaxTransactionCommitTimeoutInSeconds = 5; 16 LTMEnlistmentTests()17 public LTMEnlistmentTests() 18 { 19 } 20 21 [Theory] 22 [InlineData(1, EnlistmentOptions.None, Phase1Vote.Prepared, SinglePhaseVote.Committed, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] 23 [InlineData(2, EnlistmentOptions.None, Phase1Vote.Prepared, SinglePhaseVote.Committed, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] 24 [InlineData(1, EnlistmentOptions.None, Phase1Vote.Prepared, SinglePhaseVote.Aborted, true, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] 25 [InlineData(2, EnlistmentOptions.None, Phase1Vote.Prepared, SinglePhaseVote.Aborted, true, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] 26 [InlineData(1, EnlistmentOptions.EnlistDuringPrepareRequired, Phase1Vote.Prepared, SinglePhaseVote.Committed, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] 27 [InlineData(2, EnlistmentOptions.EnlistDuringPrepareRequired, Phase1Vote.Prepared, SinglePhaseVote.Committed, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] SinglePhaseDurable(int volatileCount, EnlistmentOptions volatileEnlistmentOption, Phase1Vote volatilePhase1Vote, SinglePhaseVote singlePhaseVote, bool commit, EnlistmentOutcome expectedVolatileOutcome, TransactionStatus expectedTxStatus)28 public void SinglePhaseDurable(int volatileCount, EnlistmentOptions volatileEnlistmentOption, Phase1Vote volatilePhase1Vote, SinglePhaseVote singlePhaseVote, bool commit, EnlistmentOutcome expectedVolatileOutcome, TransactionStatus expectedTxStatus) 29 { 30 Transaction tx = null; 31 try 32 { 33 using (TransactionScope ts = new TransactionScope()) 34 { 35 tx = Transaction.Current.Clone(); 36 37 if (volatileCount > 0) 38 { 39 TestSinglePhaseEnlistment[] volatiles = new TestSinglePhaseEnlistment[volatileCount]; 40 for (int i = 0; i < volatileCount; i++) 41 { 42 // It doesn't matter what we specify for SinglePhaseVote. 43 volatiles[i] = new TestSinglePhaseEnlistment(volatilePhase1Vote, SinglePhaseVote.InDoubt, expectedVolatileOutcome); 44 tx.EnlistVolatile(volatiles[i], volatileEnlistmentOption); 45 } 46 } 47 48 // Doesn't really matter what we specify for EnlistmentOutcome here. This is an SPC, so Phase2 won't happen for this enlistment. 49 TestSinglePhaseEnlistment durable = new TestSinglePhaseEnlistment(Phase1Vote.Prepared, singlePhaseVote, EnlistmentOutcome.Committed); 50 tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); 51 52 if (commit) 53 { 54 ts.Complete(); 55 } 56 } 57 } 58 catch (TransactionInDoubtException) 59 { 60 Assert.Equal(expectedTxStatus, TransactionStatus.InDoubt); 61 } 62 catch (TransactionAbortedException) 63 { 64 Assert.Equal(expectedTxStatus, TransactionStatus.Aborted); 65 } 66 67 68 Assert.NotNull(tx); 69 Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status); 70 } 71 72 [Theory] 73 // TODO: Issue #10353 - This test needs to change once we have promotion support. 74 // Right now any attempt to create a two phase durable enlistment will attempt to promote and will fail because promotion is not supported. This results in the transaction being 75 // aborted. 76 [InlineData(0, EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, Phase1Vote.Prepared, true, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] 77 [InlineData(1, EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, Phase1Vote.Prepared, true, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] 78 [InlineData(2, EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, Phase1Vote.Prepared, true, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] 79 [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Expects PNSE due to not being supported on core")] TwoPhaseDurable(int volatileCount, EnlistmentOptions volatileEnlistmentOption, EnlistmentOptions durableEnlistmentOption, Phase1Vote volatilePhase1Vote, Phase1Vote durablePhase1Vote, bool commit, EnlistmentOutcome expectedVolatileOutcome, EnlistmentOutcome expectedDurableOutcome, TransactionStatus expectedTxStatus)80 public void TwoPhaseDurable(int volatileCount, EnlistmentOptions volatileEnlistmentOption, EnlistmentOptions durableEnlistmentOption, Phase1Vote volatilePhase1Vote, Phase1Vote durablePhase1Vote, bool commit, EnlistmentOutcome expectedVolatileOutcome, EnlistmentOutcome expectedDurableOutcome, TransactionStatus expectedTxStatus) 81 { 82 Transaction tx = null; 83 try 84 { 85 using (TransactionScope ts = new TransactionScope()) 86 { 87 tx = Transaction.Current.Clone(); 88 89 if (volatileCount > 0) 90 { 91 TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; 92 for (int i = 0; i < volatileCount; i++) 93 { 94 // It doesn't matter what we specify for SinglePhaseVote. 95 volatiles[i] = new TestEnlistment(volatilePhase1Vote, expectedVolatileOutcome); 96 tx.EnlistVolatile(volatiles[i], volatileEnlistmentOption); 97 } 98 } 99 100 TestEnlistment durable = new TestEnlistment(Phase1Vote.Prepared, expectedDurableOutcome); 101 // TODO: Issue #10353 - This needs to change once we have promotion support. 102 Assert.Throws<PlatformNotSupportedException>(() => // Creation of two phase durable enlistment attempts to promote to MSDTC 103 { 104 tx.EnlistDurable(Guid.NewGuid(), durable, durableEnlistmentOption); 105 }); 106 107 if (commit) 108 { 109 ts.Complete(); 110 } 111 } 112 } 113 catch (TransactionInDoubtException) 114 { 115 Assert.Equal(expectedTxStatus, TransactionStatus.InDoubt); 116 } 117 catch (TransactionAbortedException) 118 { 119 Assert.Equal(expectedTxStatus, TransactionStatus.Aborted); 120 } 121 122 Assert.NotNull(tx); 123 Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status); 124 } 125 126 [Theory] 127 [InlineData(EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.None, Phase1Vote.Prepared, true, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] 128 [InlineData(EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, false, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] EnlistDuringPhase0(EnlistmentOptions enlistmentOption, EnlistmentOptions phase0EnlistmentOption, Phase1Vote phase1Vote, bool expectPhase0EnlistSuccess, bool commit, EnlistmentOutcome expectedOutcome, TransactionStatus expectedTxStatus)129 public void EnlistDuringPhase0(EnlistmentOptions enlistmentOption, EnlistmentOptions phase0EnlistmentOption, Phase1Vote phase1Vote, bool expectPhase0EnlistSuccess, bool commit, EnlistmentOutcome expectedOutcome, TransactionStatus expectedTxStatus) 130 { 131 Transaction tx = null; 132 AutoResetEvent outcomeEvent = null; 133 try 134 { 135 using (TransactionScope ts = new TransactionScope()) 136 { 137 tx = Transaction.Current.Clone(); 138 outcomeEvent = new AutoResetEvent(false); 139 TestEnlistment enlistment = new TestEnlistment(phase1Vote, expectedOutcome, true, expectPhase0EnlistSuccess, outcomeEvent); 140 tx.EnlistVolatile(enlistment, enlistmentOption); 141 142 if (commit) 143 { 144 ts.Complete(); 145 } 146 } 147 } 148 catch (TransactionInDoubtException) 149 { 150 Assert.Equal(expectedTxStatus, TransactionStatus.InDoubt); 151 } 152 catch (TransactionAbortedException) 153 { 154 Assert.Equal(expectedTxStatus, TransactionStatus.Aborted); 155 } 156 157 Assert.True(outcomeEvent.WaitOne(TimeSpan.FromSeconds(MaxTransactionCommitTimeoutInSeconds))); 158 Assert.NotNull(tx); 159 Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status); 160 } 161 162 [Theory] 163 [InlineData(5, EnlistmentOptions.None, Phase1Vote.Prepared, Phase1Vote.Prepared, true, EnlistmentOutcome.Committed, TransactionStatus.Committed)] 164 [InlineData(5, EnlistmentOptions.None, Phase1Vote.Prepared, Phase1Vote.ForceRollback, true, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] EnlistVolatile(int volatileCount, EnlistmentOptions enlistmentOption, Phase1Vote volatilePhase1Vote, Phase1Vote lastPhase1Vote, bool commit, EnlistmentOutcome expectedEnlistmentOutcome, TransactionStatus expectedTxStatus)165 public void EnlistVolatile(int volatileCount, EnlistmentOptions enlistmentOption, Phase1Vote volatilePhase1Vote, Phase1Vote lastPhase1Vote, bool commit, EnlistmentOutcome expectedEnlistmentOutcome, TransactionStatus expectedTxStatus) 166 { 167 AutoResetEvent[] outcomeEvents = null; 168 Transaction tx = null; 169 try 170 { 171 using (TransactionScope ts = new TransactionScope()) 172 { 173 tx = Transaction.Current.Clone(); 174 175 if (volatileCount > 0) 176 { 177 TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; 178 outcomeEvents = new AutoResetEvent[volatileCount]; 179 for (int i = 0; i < volatileCount-1; i++) 180 { 181 outcomeEvents[i] = new AutoResetEvent(false); 182 volatiles[i] = new TestEnlistment(volatilePhase1Vote, expectedEnlistmentOutcome, false, true, outcomeEvents[i]); 183 tx.EnlistVolatile(volatiles[i], enlistmentOption); 184 } 185 186 outcomeEvents[volatileCount-1] = new AutoResetEvent(false); 187 volatiles[volatileCount - 1] = new TestEnlistment(lastPhase1Vote, expectedEnlistmentOutcome, false, true, outcomeEvents[volatileCount-1]); 188 tx.EnlistVolatile(volatiles[volatileCount - 1], enlistmentOption); 189 } 190 191 if (commit) 192 { 193 ts.Complete(); 194 } 195 } 196 } 197 catch (TransactionInDoubtException) 198 { 199 Assert.Equal(expectedTxStatus, TransactionStatus.InDoubt); 200 } 201 catch (TransactionAbortedException) 202 { 203 Assert.Equal(expectedTxStatus, TransactionStatus.Aborted); 204 } 205 206 Assert.True(AutoResetEvent.WaitAll(outcomeEvents, TimeSpan.FromSeconds(MaxTransactionCommitTimeoutInSeconds))); 207 Assert.NotNull(tx); 208 Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status); 209 } 210 211 } 212 } 213