public void Distributed_rollback() { var disposedCalled = false; var tx = new TransactionScope(); try { using (var conn1 = OpenConnection(ConnectionStringEnlistOn)) { Assert.That(conn1.ExecuteNonQuery(@"INSERT INTO data (name) VALUES ('test1')"), Is.EqualTo(1), "Unexpected first insert rowcount"); EnlistResource.EscalateToDistributed(true); AssertHasDistributedIdentifier(); tx.Complete(); } disposedCalled = true; Assert.That(() => tx.Dispose(), Throws.TypeOf <TransactionAbortedException>()); // TODO: There may be a race condition here, where the prepared transaction above still hasn't completed. AssertNoDistributedIdentifier(); AssertNoPreparedTransactions(); AssertNumberOfRows(0); } finally { if (!disposedCalled) { tx.Dispose(); } } }
public void NonDistributedInDoubt() { try { using (var scope = new TransactionScope()) { _log.InfoFormat( "Scope opened, id {0}, distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate a simple connection: durable resource supporting single phase. // (Note that SQL Server 2005 and above use IPromotableSinglePhaseNotification // for delegating the resource management to the SQL server.) EnlistResource.EnlistInDoubtDurable(); _log.InfoFormat( "Fake connection opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate NHibernate : volatile no single phase support + enlist in prepare option EnlistResource.EnlistWithPrepareEnlistmentVolatile(); _log.InfoFormat( "Fake session opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); scope.Complete(); _log.Info("Scope completed"); } } catch (TransactionInDoubtException) { // expected } _log.Info("Scope disposed"); }
public void NonDistributedNpgsqlCommit([Values(false, true)] bool enlistInPrepare) { using (var scope = new TransactionScope()) { _log.InfoFormat( "Scope opened, id {0}, distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate a Npgsql connection: as of Npgsql 3.2.4, volatile resource with single phase support EnlistResource.EnlistVolatile(false, true); _log.InfoFormat( "Fake connection opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate NHibernate : volatile no single phase support if (enlistInPrepare) { EnlistResource.EnlistWithPrepareEnlistmentVolatile(); } else { EnlistResource.EnlistVolatile(); } _log.InfoFormat( "Fake session opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); scope.Complete(); _log.Info("Scope completed"); } _log.Info("Scope disposed"); }
public void NonDistributedCommit([Values(false, true)] bool enlistInPrepare) { using (var scope = new TransactionScope()) { _log.InfoFormat( "Scope opened, id {0}, distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate a simple connection: durable resource supporting single phase. // (Note that SQL Server 2005 and above use IPromotableSinglePhaseNotification // for delegating the resource management to the SQL server.) EnlistResource.EnlistDurable(false, true); _log.InfoFormat( "Fake connection opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate NHibernate : volatile no single phase support if (enlistInPrepare) { EnlistResource.EnlistWithPrepareEnlistmentVolatile(); } else { EnlistResource.EnlistVolatile(); } _log.InfoFormat( "Fake session opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); scope.Complete(); _log.Info("Scope completed"); } _log.Info("Scope disposed"); }
public void DistributedFailureInSecondPhase() { using (var scope = new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromSeconds(5))) { _log.InfoFormat( "Scope opened, id {0}, distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate a failing connection EnlistResource.EnlistSecondPhaseFailingDurable(); _log.InfoFormat( "Fake connection opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate another resource, not even supporting single phase EnlistResource.EnlistDurable(); _log.InfoFormat( "Fake other resource, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate NHibernate : volatile no single phase support + enlist in prepare option EnlistResource.EnlistWithPrepareEnlistmentVolatile(); _log.InfoFormat( "Fake session opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); scope.Complete(); _log.Info("Scope completed"); } _log.Info("Scope disposed"); }
public void DistributedRollback([Values(false, true)] bool fromConnection, [Values(false, true)] bool fromOther, [Values(false, true)] bool fromSession) { var shouldFail = fromConnection || fromSession || fromOther; try { using (var scope = new TransactionScope()) { _log.InfoFormat( "Scope opened, id {0}, distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate a simple connection: durable resource supporting single phase. // (Note that SQL Server 2005 and above use IPromotableSinglePhaseNotification // for delegating the resource management to the SQL server.) EnlistResource.EnlistDurable(fromConnection, true); _log.InfoFormat( "Fake connection opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate another resource, not even supporting single phase EnlistResource.EnlistDurable(fromOther); _log.InfoFormat( "Fake other resource, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate NHibernate : volatile no single phase support + enlist in prepare option EnlistResource.EnlistWithPrepareEnlistmentVolatile(fromSession); _log.InfoFormat( "Fake session opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); if (shouldFail) { scope.Complete(); _log.Info("Scope completed"); } else { _log.Info("Scope not completed for triggering rollback"); } } } catch (TransactionAbortedException) { if (!shouldFail) { throw; } } _log.Info("Scope disposed"); }
public void DistributedNpgsqlRollback([Values(false, true)] bool fromConnection, [Values(false, true)] bool fromOther, [Values(false, true)] bool fromSession) { var shouldFail = fromConnection || fromSession || fromOther; try { using (var scope = new TransactionScope()) { _log.InfoFormat( "Scope opened, id {0}, distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate a Npgsql connection: as of Npgsql 3.2.4, volatile resource with single phase support EnlistResource.EnlistVolatile(fromConnection, true); _log.InfoFormat( "Fake connection opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate another resource, not even supporting single phase (required for going distributed with "Npgsql") EnlistResource.EnlistDurable(fromOther); _log.InfoFormat( "Fake other resource, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); // Simulate NHibernate : volatile no single phase support + enlist in prepare option EnlistResource.EnlistWithPrepareEnlistmentVolatile(fromSession); _log.InfoFormat( "Fake session opened, scope id {0} and distributed id {1}", SysTran.Current.TransactionInformation.LocalIdentifier, SysTran.Current.TransactionInformation.DistributedIdentifier); if (shouldFail) { scope.Complete(); _log.Info("Scope completed"); } else { _log.Info("Scope not completed for triggering rollback"); } } } catch (TransactionAbortedException) { if (!shouldFail) { throw; } } _log.Info("Scope disposed"); }
public void Connection_reuse_race_after_rollback([Values(false, true)] bool distributed) { for (var i = 1; i <= 100; i++) { var eventQueue = new ConcurrentQueue <TransactionEvent>(); try { using (var conn1 = OpenConnection(ConnectionStringEnlistOff)) { using (new TransactionScope()) { conn1.EnlistTransaction(Transaction.Current); eventQueue.Enqueue(new TransactionEvent("Scope started, connection enlisted")); if (distributed) { EnlistResource.EscalateToDistributed(eventQueue); AssertHasDistributedIdentifier(); } else { EnlistResource.EnlistVolatile(eventQueue); AssertNoDistributedIdentifier(); } Assert.That(conn1.ExecuteNonQuery(@"INSERT INTO data (name) VALUES ('test1')"), Is.EqualTo(1), "Unexpected first insert rowcount"); eventQueue.Enqueue(new TransactionEvent("Insert done")); eventQueue.Enqueue(new TransactionEvent("Scope not completed")); } eventQueue.Enqueue(new TransactionEvent("Scope disposed")); conn1.EnlistTransaction(null); eventQueue.Enqueue(new TransactionEvent("Connection enlisted with null")); Assert.DoesNotThrow(() => conn1.ExecuteScalar(@"SELECT COUNT(*) FROM data")); } } catch (Exception ex) { Assert.Fail( @"Failed at iteration {0}. Events: {1} Exception {2}", i, FormatEventQueue(eventQueue), ex); } } }
static void Enlist(bool durable, bool shouldRollBack, ConcurrentQueue <TransactionEvent>?eventQueue) { Counter++; var name = $"{(durable ? "Durable" : "Volatile")} resource {Counter}"; var resource = new EnlistResource(shouldRollBack, name, eventQueue); if (durable) { Transaction.Current !.EnlistDurable(Guid.NewGuid(), resource, EnlistmentOptions.None); } else { Transaction.Current !.EnlistVolatile(resource, EnlistmentOptions.None); } Transaction.Current.TransactionCompleted += resource.Current_TransactionCompleted !; eventQueue?.Enqueue(new TransactionEvent(name + ": enlisted")); }
private static void Enlist(bool durable, bool supportsSinglePhase, bool shouldRollBack, bool inDoubt = false, bool failInSecondPhase = false, bool enlistInPrepareOption = false) { Counter++; var name = $"{(durable ? "Durable" : "Volatile")} resource {Counter}"; EnlistResource resource; var options = enlistInPrepareOption ? EnlistmentOptions.EnlistDuringPrepareRequired : EnlistmentOptions.None; if (supportsSinglePhase) { var spResource = new EnlistSinglePhaseResource(shouldRollBack, name, inDoubt, failInSecondPhase); resource = spResource; if (durable) { SysTran.Current.EnlistDurable(Guid.NewGuid(), spResource, options); } else { SysTran.Current.EnlistVolatile(spResource, options); } } else { resource = new EnlistResource(shouldRollBack, name, inDoubt, failInSecondPhase); // Not duplicate code with above, that is not the same overload which ends up called. if (durable) { SysTran.Current.EnlistDurable(Guid.NewGuid(), resource, options); } else { SysTran.Current.EnlistVolatile(resource, options); } } SysTran.Current.TransactionCompleted += resource.Current_TransactionCompleted; _log.Info(name + ": enlisted"); }
public void Transaction_race([Values(false, true)] bool distributed) { for (var i = 1; i <= 100; i++) { var eventQueue = new ConcurrentQueue <TransactionEvent>(); try { using (var tx = new TransactionScope()) using (var conn1 = OpenConnection(ConnectionStringEnlistOn)) { eventQueue.Enqueue(new TransactionEvent("Scope started, connection enlisted")); Assert.That(conn1.ExecuteNonQuery(@"INSERT INTO data (name) VALUES ('test1')"), Is.EqualTo(1), "Unexpected first insert rowcount"); eventQueue.Enqueue(new TransactionEvent("Insert done")); if (distributed) { EnlistResource.EscalateToDistributed(eventQueue); AssertHasDistributedIdentifier(); } else { EnlistResource.EnlistVolatile(eventQueue); AssertNoDistributedIdentifier(); } tx.Complete(); eventQueue.Enqueue(new TransactionEvent("Scope completed")); } eventQueue.Enqueue(new TransactionEvent("Scope disposed")); AssertNoDistributedIdentifier(); if (distributed) { // There may be a race condition here, where the prepared transaction above still hasn't completed. // This is by design of MS DTC. Giving it up to 100ms to complete. If it proves flaky, raise // maxLoop. const int maxLoop = 20; for (var j = 0; j < maxLoop; j++) { Thread.Sleep(10); try { AssertNumberOfRows(i); break; } catch { if (j == maxLoop - 1) { throw; } } } } else { AssertNumberOfRows(i); } } catch (Exception ex) { Assert.Fail( @"Failed at iteration {0}. Events: {1} Exception {2}", i, FormatEventQueue(eventQueue), ex); } } }
public void Connection_reuse_race_chaining_transaction([Values(false, true)] bool distributed) { for (var i = 1; i <= 100; i++) { var eventQueue = new ConcurrentQueue <TransactionEvent>(); try { using (var conn1 = OpenConnection(ConnectionStringEnlistOff)) { using (var scope = new TransactionScope()) { eventQueue.Enqueue(new TransactionEvent("First scope started")); conn1.EnlistTransaction(Transaction.Current); eventQueue.Enqueue(new TransactionEvent("First scope, connection enlisted")); if (distributed) { EnlistResource.EscalateToDistributed(eventQueue); AssertHasDistributedIdentifier(); } else { EnlistResource.EnlistVolatile(eventQueue); AssertNoDistributedIdentifier(); } Assert.That(conn1.ExecuteNonQuery(@"INSERT INTO data (name) VALUES ('test1')"), Is.EqualTo(1), "Unexpected first insert rowcount"); eventQueue.Enqueue(new TransactionEvent("First insert done")); scope.Complete(); eventQueue.Enqueue(new TransactionEvent("First scope completed")); } eventQueue.Enqueue(new TransactionEvent("First scope disposed")); using (var scope = new TransactionScope()) { eventQueue.Enqueue(new TransactionEvent("Second scope started")); conn1.EnlistTransaction(Transaction.Current); eventQueue.Enqueue(new TransactionEvent("Second scope, connection enlisted")); if (distributed) { EnlistResource.EscalateToDistributed(eventQueue); AssertHasDistributedIdentifier(); } else { EnlistResource.EnlistVolatile(eventQueue); AssertNoDistributedIdentifier(); } Assert.That(conn1.ExecuteNonQuery(@"INSERT INTO data (name) VALUES ('test1')"), Is.EqualTo(1), "Unexpected second insert rowcount"); eventQueue.Enqueue(new TransactionEvent("Second insert done")); scope.Complete(); eventQueue.Enqueue(new TransactionEvent("Second scope completed")); } eventQueue.Enqueue(new TransactionEvent("Second scope disposed")); } } catch (Exception ex) { Assert.Fail( @"Failed at iteration {0}. Events: {1} Exception {2}", i, FormatEventQueue(eventQueue), ex); } } }