public async Task RollbackOutsideNhAsync(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); try { using (var txscope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { using (var s = OpenSession()) { var person = new Person(); await(s.SaveAsync(person)); if (explicitFlush) { await(s.FlushAsync()); } } ForceEscalationToDistributedTx.Escalate(true); //will rollback tx txscope.Complete(); } Assert.Fail("Scope disposal has not rollback and throw."); } catch (TransactionAbortedException) { _log.Debug("Transaction aborted."); } AssertNoPersons(); }
public void EnforceConnectionUsageRulesOnTransactionCompletion() { var interceptor = new TransactionCompleteUsingConnectionInterceptor(); // Do not invert session and scope, it would cause an expected failure when // UseConnectionOnSystemTransactionEvents is false, due to the session being closed. using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) using (var tx = new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); if (!AutoJoinTransaction) { s.JoinTransaction(); } s.Save(new Person()); s.Flush(); tx.Complete(); } if (UseConnectionOnSystemTransactionPrepare) { Assert.That(interceptor.BeforeException, Is.Null); } else { Assert.That(interceptor.BeforeException, Is.TypeOf <HibernateException>()); } // Currently always forbidden, whatever UseConnectionOnSystemTransactionEvents. Assert.That(interceptor.AfterException, Is.TypeOf <HibernateException>()); }
public void TransactionInsertWithRollBackTask(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); try { using (var txscope = new TransactionScope()) { using (var s = OpenSession()) { var person = new Person(); s.Save(person); ForceEscalationToDistributedTx.Escalate(true); //will rollback tx if (explicitFlush) { s.Flush(); } } txscope.Complete(); } Assert.Fail("Scope disposal has not rollback and throw."); } catch (TransactionAbortedException) { _log.Debug("Transaction aborted."); } AssertNoPersons(); }
public void AdditionalJoinDoesNotThrow() { using (new TransactionScope()) using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(); Assert.DoesNotThrow(() => s.JoinTransaction()); } }
public void TransactionInsertLoadWithRollBackTask(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); object savedId; var createdAt = DateTime.Today; using (var txscope = new TransactionScope()) { using (var s = OpenSession()) { var person = new Person { CreatedAt = createdAt }; savedId = s.Save(person); if (explicitFlush) { s.Flush(); } } txscope.Complete(); } try { using (var txscope = new TransactionScope()) { using (var s = OpenSession()) { var person = s.Get <Person>(savedId); person.CreatedAt = createdAt.AddMonths(-1); if (explicitFlush) { s.Flush(); } } ForceEscalationToDistributedTx.Escalate(true); _log.Debug("completing the tx scope"); txscope.Complete(); } _log.Debug("Transaction fail."); Assert.Fail("Expected tx abort"); } catch (TransactionAbortedException) { _log.Debug("Transaction aborted."); } using (var s = OpenSession()) using (s.BeginTransaction()) { Assert.AreEqual(createdAt, s.Get <Person>(savedId).CreatedAt, "Entity update was not rollback-ed."); } }
public void SessionIsNotEnlisted() { using (new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); // Dodge the OpenSession override which call JoinTransaction by calling WithOptions(). using (var s = WithOptions().OpenSession()) { Assert.That(s.GetSessionImplementation().TransactionContext, Is.Null); } } }
public void DelayedTransactionCompletion(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); for (var i = 1; i <= 10; i++) { // Isolation level must be read committed on the control session: reading twice while expecting some data insert // in between due to a late commit. Repeatable read would block and read uncommitted would see the uncommitted data. using (var controlSession = OpenSession()) using (controlSession.BeginTransaction(System.Data.IsolationLevel.ReadCommitted)) { // We want to have the control session as ready to query as possible, thus beginning its // transaction early for acquiring the connection, even if we will not use it before // below scope completion. using (var tx = new TransactionScope()) { using (var s = OpenSession()) { s.Save(new Person()); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { s.Flush(); } } tx.Complete(); } var count = controlSession.Query <Person>().Count(); if (count != i) { // See https://github.com/npgsql/npgsql/issues/1571#issuecomment-308651461 discussion with a Microsoft // employee: MSDTC consider a transaction to be committed once it has collected all participant votes // for committing from prepare phase. It then immediately notifies all participants of the outcome. // This causes TransactionScope.Dispose to leave while the second phase of participants may still // be executing. This means the transaction from the db view point can still be pending and not yet // committed. This is by design of MSDTC and we have to cope with that. Some data provider may have // a global locking mechanism causing any subsequent use to wait for the end of the commit phase, // but this is not the usual case. Some other, as Npgsql < v3.2.5, may crash due to this, because // they re-use the connection for the second phase. Thread.Sleep(100); var countSecondTry = controlSession.Query <Person>().Count(); Assert.Warn($"Unexpected entity count: {count} instead of {i}. " + "This may mean current data provider has a delayed commit, occurring after scope disposal. " + $"After waiting, count is now {countSecondTry}. "); break; } } } }
public void WhenCommittingItemsAfterSessionDisposalWillAddThemTo2ndLevelCache() { int id; const string notNullData = "test"; using (var tx = new TransactionScope()) { using (var s = OpenSession()) { var person = new CacheablePerson { NotNullData = notNullData }; s.Save(person); id = person.Id; ForceEscalationToDistributedTx.Escalate(); s.Flush(); } tx.Complete(); } DodgeTransactionCompletionDelayIfRequired(); using (var tx = new TransactionScope()) { using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(); var person = s.Load <CacheablePerson>(id); Assert.That(person.NotNullData, Is.EqualTo(notNullData)); } tx.Complete(); } // Closing the connection to ensure we can't actually use it. var connection = Sfi.ConnectionProvider.GetConnection(); Sfi.ConnectionProvider.CloseConnection(connection); // The session is supposed to succeed because the second level cache should have the // entity to load, allowing the session to not use the connection at all. // Will fail if a transaction manager tries to enlist user supplied connection. Do // not add a transaction scope below. using (var s = WithOptions().Connection(connection).OpenSession()) { CacheablePerson person = null; Assert.DoesNotThrow(() => person = s.Load <CacheablePerson>(id), "Failed loading entity from second level cache."); Assert.That(person.NotNullData, Is.EqualTo(notNullData)); } }
public async Task CanDeleteItemInDtcAsync(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); object id; using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { using (var s = OpenSession()) { id = await(s.SaveAsync(new Person())); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { await(s.FlushAsync()); } tx.Complete(); } } DodgeTransactionCompletionDelayIfRequired(); using (var s = OpenSession()) using (s.BeginTransaction()) { Assert.AreEqual(1, await(s.Query <Person>().CountAsync()), "Entity not found in database."); } using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(); await(s.DeleteAsync(await(s.GetAsync <Person>(id)))); if (explicitFlush) { await(s.FlushAsync()); } tx.Complete(); } } DodgeTransactionCompletionDelayIfRequired(); AssertNoPersons(); }
public void CanDeleteItemInDtc(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); object id; using (var tx = new TransactionScope()) { using (var s = OpenSession()) { id = s.Save(new Person()); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { s.Flush(); } tx.Complete(); } } DodgeTransactionCompletionDelayIfRequired(); using (var s = OpenSession()) using (s.BeginTransaction()) { Assert.AreEqual(1, s.Query <Person>().Count(), "Entity not found in database."); } using (var tx = new TransactionScope()) { using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(); s.Delete(s.Get <Person>(id)); if (explicitFlush) { s.Flush(); } tx.Complete(); } } DodgeTransactionCompletionDelayIfRequired(); AssertNoPersons(); }
public void TransactionInsertLoadWithRollBackFromScope(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); object savedId; var createdAt = DateTime.Today; using (var txscope = new TransactionScope()) { using (var s = OpenSession()) { var person = new Person { CreatedAt = createdAt }; savedId = s.Save(person); if (explicitFlush) { s.Flush(); } } txscope.Complete(); } using (new TransactionScope()) { using (var s = OpenSession()) { var person = s.Get <Person>(savedId); person.CreatedAt = createdAt.AddMonths(-1); if (explicitFlush) { s.Flush(); } } ForceEscalationToDistributedTx.Escalate(); // No Complete call for triggering rollback. } using (var s = OpenSession()) using (s.BeginTransaction()) { Assert.AreEqual(createdAt, s.Get <Person>(savedId).CreatedAt, "Entity update was not rollback-ed."); } }
public void SupportsPromotingToDistributed() { using (new TransactionScope()) { using (var s = OpenSession()) { s.Save(new Person()); // Ensure the connection is acquired (thus enlisted) s.Flush(); } Assert.DoesNotThrow(() => ForceEscalationToDistributedTx.Escalate(), "Failure promoting the transaction to distributed while already having enlisted a connection."); Assert.AreNotEqual( Guid.Empty, System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier, "Transaction lacks a distributed identifier"); } }
public async Task CanRollbackTransactionFromScopeAsync(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(); await(s.SaveAsync(new Person())); if (explicitFlush) { await(s.FlushAsync()); } // No Complete call for triggering rollback. } AssertNoPersons(); }
public void CanRollbackTransactionFromScope(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); using (new TransactionScope()) using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(); s.Save(new Person()); if (explicitFlush) { s.Flush(); } // No Complete call for triggering rollback. } AssertNoPersons(); }
public async Task SupportsEnlistingInDistributedAsync() { using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { ForceEscalationToDistributedTx.Escalate(); Assert.AreNotEqual( Guid.Empty, System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier, "Transaction lacks a distributed identifier"); using (var s = OpenSession()) { await(s.SaveAsync(new Person())); // Ensure the connection is acquired (thus enlisted) Assert.DoesNotThrowAsync(() => s.FlushAsync(), "Failure enlisting a connection in a distributed transaction."); } } }
public void SupportsEnlistingInDistributed() { using (new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); Assert.AreNotEqual( Guid.Empty, System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier, "Transaction lacks a distributed identifier"); using (var s = OpenSession()) { s.Save(new Person()); // Ensure the connection is acquired (thus enlisted) Assert.DoesNotThrow(s.Flush, "Failure enlisting a connection in a distributed transaction."); } } }
public void DoNotDeadlockOnAfterTransactionWait() { var interceptor = new AfterTransactionWaitingInterceptor(); using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) using (var tx = new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); if (!AutoJoinTransaction) { s.JoinTransaction(); } s.Save(new Person()); s.Flush(); tx.Complete(); } Assert.That(interceptor.Exception, Is.Null); }
public async Task DoNotDeadlockOnAfterTransactionWaitAsync() { var interceptor = new AfterTransactionWaitingInterceptor(); using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { ForceEscalationToDistributedTx.Escalate(); if (!AutoJoinTransaction) { s.JoinTransaction(); } await(s.SaveAsync(new Person())); await(s.FlushAsync()); tx.Complete(); } Assert.That(interceptor.Exception, Is.Null); }
public void TransactionInsertWithRollBackFromScope(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); using (new TransactionScope()) { using (var s = OpenSession()) { var person = new Person(); s.Save(person); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { s.Flush(); } } // No Complete call for triggering rollback. } AssertNoPersons(); }
public void CanRollbackTransaction(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); var tx = new TransactionScope(); var disposeCalled = false; try { using (var s = OpenSession()) { ForceEscalationToDistributedTx.Escalate(true); //will rollback tx s.Save(new Person()); if (explicitFlush) { s.Flush(); } tx.Complete(); } disposeCalled = true; Assert.Throws <TransactionAbortedException>(tx.Dispose, "Scope disposal has not rollback and throw."); } finally { if (!disposeCalled) { try { tx.Dispose(); } catch { // Ignore, if disposed has not been called, another exception has occurred in the try and // we should avoid overriding it by the disposal failure. } } } AssertNoPersons(); }
public void WillNotCrashOnPrepareFailure() { IgnoreIfUnsupported(false); var tx = new TransactionScope(); var disposeCalled = false; try { using (var s = OpenSession()) { s.Save(new Person { NotNullData = null }); // Cause a SQL not null constraint violation. } ForceEscalationToDistributedTx.Escalate(); tx.Complete(); disposeCalled = true; Assert.Throws <TransactionAbortedException>(tx.Dispose, "Scope disposal has not rollback and throw."); } finally { if (!disposeCalled) { try { tx.Dispose(); } catch { // Ignore, if disposed has not been called, another exception has occurred in the try and // we should avoid overriding it by the disposal failure. } } } }
public void CanUseSessionOutsideOfScopeAfterScope(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); // Note that this fails with ConnectionReleaseMode.OnClose and Npgsql (< 3.2.5?): // NpgsqlOperationInProgressException: The connection is already in state 'Executing' // Not much an issue since it is advised to not use ConnectionReleaseMode.OnClose. using (var s = OpenSession()) //using (var s = WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession()) { using (var tx = new TransactionScope()) { if (!AutoJoinTransaction) { s.JoinTransaction(); } s.Save(new Person()); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { s.Flush(); } tx.Complete(); } var count = 0; Assert.DoesNotThrow(() => count = s.Query <Person>().Count(), "Failed using the session after scope."); if (count != 1) { // We are not testing that here, so just issue a warning. Do not use DodgeTransactionCompletionDelayIfRequired // before previous assert. We want to ascertain the session is usable in any cases. Assert.Warn("Unexpected entity count: {0} instead of {1}. The transaction seems to have a delayed commit.", count, 1); } } }
public void CanUseSessionWithManyScopes(bool explicitFlush) { IgnoreIfUnsupported(explicitFlush); // Note that this fails with ConnectionReleaseMode.OnClose and SqlServer: // System.Data.SqlClient.SqlException : Microsoft Distributed Transaction Coordinator (MS DTC) has stopped this transaction. // Not much an issue since it is advised to not use ConnectionReleaseMode.OnClose. using (var s = OpenSession()) //using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession()) { using (var tx = new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); if (!AutoJoinTransaction) { s.JoinTransaction(); } // Acquire the connection var count = s.Query <Person>().Count(); Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count."); tx.Complete(); } // No dodge here please! Allow to check chaining usages do not fail. using (var tx = new TransactionScope()) { if (!AutoJoinTransaction) { s.JoinTransaction(); } s.Save(new Person()); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { s.Flush(); } tx.Complete(); } DodgeTransactionCompletionDelayIfRequired(); using (var tx = new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); if (!AutoJoinTransaction) { s.JoinTransaction(); } var count = s.Query <Person>().Count(); Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert."); tx.Complete(); } using (new TransactionScope()) { if (!AutoJoinTransaction) { s.JoinTransaction(); } s.Save(new Person()); ForceEscalationToDistributedTx.Escalate(); if (explicitFlush) { s.Flush(); } // No complete for rollback-ing. } DodgeTransactionCompletionDelayIfRequired(); // Do not reuse the session after a rollback, its state does not allow it. // http://nhibernate.info/doc/nhibernate-reference/manipulatingdata.html#manipulatingdata-endingsession-commit } using (var s = OpenSession()) { using (var tx = new TransactionScope()) { ForceEscalationToDistributedTx.Escalate(); if (!AutoJoinTransaction) { s.JoinTransaction(); } var count = s.Query <Person>().Count(); Assert.That(count, Is.EqualTo(1), "Unexpected entity count after rollback-ed insert."); tx.Complete(); } } }