From bfbd264c1ceeac9e596b4005a4b6a8cd60c98478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Tue, 20 Jun 2017 16:03:31 +0200 Subject: [PATCH 1/2] NH-4034 - flushing sessions sharing transaction on commit. --- src/NHibernate.Test/DebugSessionFactory.cs | 45 +++++---- src/NHibernate.Test/NHibernate.Test.csproj | 3 + .../SystemTransactions/TransactionFixture.cs | 92 ++++++++++++++++--- src/NHibernate.Test/TransactionTest/Person.cs | 11 +++ .../TransactionTest/Person.hbm.xml | 12 +++ .../TransactionTest/TransactionFixture.cs | 55 ++++++++--- .../TransactionTest/TransactionFixtureBase.cs | 39 ++++++++ src/NHibernate/AdoNet/ConnectionManager.cs | 22 +++++ src/NHibernate/ISharedSessionBuilder.cs | 2 + src/NHibernate/Impl/SessionImpl.cs | 40 ++++++-- ...AdoNetWithDistributedTransactionFactory.cs | 64 +++++++++++-- src/NHibernate/Transaction/AdoTransaction.cs | 7 ++ 12 files changed, 337 insertions(+), 55 deletions(-) create mode 100644 src/NHibernate.Test/TransactionTest/Person.cs create mode 100644 src/NHibernate.Test/TransactionTest/Person.hbm.xml create mode 100644 src/NHibernate.Test/TransactionTest/TransactionFixtureBase.cs diff --git a/src/NHibernate.Test/DebugSessionFactory.cs b/src/NHibernate.Test/DebugSessionFactory.cs index dfb6e5ce571..801b8f3a76e 100644 --- a/src/NHibernate.Test/DebugSessionFactory.cs +++ b/src/NHibernate.Test/DebugSessionFactory.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Common; +using System.Linq; using System.Threading; using log4net; using NHibernate.Cache; @@ -53,29 +54,41 @@ public bool CheckSessionsWereClosed() var allClosed = true; foreach (var session in _openedSessions) { - if (session.IsOpen) + // Do not inverse, we want to close all of them. + allClosed = CheckSessionWasClosed(session) && allClosed; + // Catches only session opened from another one while sharing the connection. Those + // opened without sharing the connection stay un-monitored. + foreach (var dependentSession in session.ConnectionManager.DependentSessions.ToList()) { - if (session.TransactionContext?.ShouldCloseSessionOnDistributedTransactionCompleted ?? false) - { - // Delayed transactions not having completed and closed their sessions? Give them a chance to complete. - Thread.Sleep(100); - if (!session.IsOpen) - { - _log.Warn($"Test case had a delayed close of session {session.SessionId}."); - continue; - } - } - - _log.Error($"Test case didn't close session {session.SessionId}, closing"); - allClosed = false; - (session as ISession)?.Close(); - (session as IStatelessSession)?.Close(); + allClosed = CheckSessionWasClosed(dependentSession) && allClosed; } } return allClosed; } + private bool CheckSessionWasClosed(ISessionImplementor session) + { + if (!session.IsOpen) + return true; + + if (session.TransactionContext?.ShouldCloseSessionOnDistributedTransactionCompleted ?? false) + { + // Delayed transactions not having completed and closed their sessions? Give them a chance to complete. + Thread.Sleep(100); + if (!session.IsOpen) + { + _log.Warn($"Test case had a delayed close of session {session.SessionId}."); + return true; + } + } + + _log.Error($"Test case didn't close session {session.SessionId}, closing"); + (session as ISession)?.Close(); + (session as IStatelessSession)?.Close(); + return false; + } + ISessionBuilder ISessionFactory.WithOptions() { return new SessionBuilder(ActualFactory.WithOptions(), this); diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index d8c04e36a8a..198851b6e86 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -1464,6 +1464,8 @@ + + @@ -3286,6 +3288,7 @@ + diff --git a/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs b/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs index cad02f86e90..86aabd4d3e0 100644 --- a/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs @@ -1,26 +1,23 @@ -using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Transactions; -using NHibernate.DomainModel; +using NHibernate.Linq; +using NHibernate.Test.TransactionTest; using NUnit.Framework; namespace NHibernate.Test.SystemTransactions { [TestFixture] - public class TransactionFixture : TestCase + public class TransactionFixture : TransactionFixtureBase { - protected override IList Mappings - { - get { return new string[] { "WZ.hbm.xml" }; } - } - [Test] public void CanUseSystemTransactionsToCommit() { - object identifier; + int identifier; using(ISession session = Sfi.OpenSession()) using(TransactionScope tx = new TransactionScope()) { - W s = new W(); + var s = new Person(); session.Save(s); identifier = s.Id; tx.Complete(); @@ -29,11 +26,84 @@ public void CanUseSystemTransactionsToCommit() using (ISession session = Sfi.OpenSession()) using (TransactionScope tx = new TransactionScope()) { - W w = session.Get(identifier); + var w = session.Get(identifier); Assert.IsNotNull(w); session.Delete(w); tx.Complete(); } } + + [Test] + public void FlushFromTransactionAppliesToDisposedSharingSession() + { + var flushOrder = new List(); + using (var s = OpenSession(new TestInterceptor(0, flushOrder))) + { + var builder = s.SessionWithOptions().Connection(); + + using (var t = new TransactionScope()) + { + var p1 = new Person(); + var p2 = new Person(); + var p3 = new Person(); + var p4 = new Person(); + + using (var s1 = builder.Interceptor(new TestInterceptor(1, flushOrder)).OpenSession()) + s1.Save(p1); + using (var s2 = builder.Interceptor(new TestInterceptor(2, flushOrder)).OpenSession()) + { + s2.Save(p2); + using (var s3 = s2.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)).OpenSession()) + s3.Save(p3); + } + s.Save(p4); + t.Complete(); + } + } + + Assert.That(flushOrder, Is.EqualTo(new[] { 1, 2, 3, 0 })); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(s.Query().Count(), Is.EqualTo(4)); + t.Commit(); + } + } + + [Test] + public void FlushFromTransactionAppliesToSharingSession() + { + var flushOrder = new List(); + using (var s = OpenSession(new TestInterceptor(0, flushOrder))) + { + var builder = s.SessionWithOptions().Connection(); + + using (var s1 = builder.Interceptor(new TestInterceptor(1, flushOrder)).OpenSession()) + using (var s2 = builder.Interceptor(new TestInterceptor(2, flushOrder)).OpenSession()) + using (var s3 = s2.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)).OpenSession()) + using (var t = new TransactionScope()) + { + var p1 = new Person(); + var p2 = new Person(); + var p3 = new Person(); + var p4 = new Person(); + s1.Save(p1); + s2.Save(p2); + s3.Save(p3); + s.Save(p4); + t.Complete(); + } + } + + Assert.That(flushOrder, Is.EqualTo(new[] { 1, 2, 3, 0 })); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(s.Query().Count(), Is.EqualTo(4)); + t.Commit(); + } + } } } \ No newline at end of file diff --git a/src/NHibernate.Test/TransactionTest/Person.cs b/src/NHibernate.Test/TransactionTest/Person.cs new file mode 100644 index 00000000000..39ad3f19626 --- /dev/null +++ b/src/NHibernate.Test/TransactionTest/Person.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.TransactionTest +{ + public class Person + { + public virtual int Id { get; set; } + + public virtual DateTime CreatedAt { get; set; } = DateTime.Now; + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/TransactionTest/Person.hbm.xml b/src/NHibernate.Test/TransactionTest/Person.hbm.xml new file mode 100644 index 00000000000..49c9ffc20e1 --- /dev/null +++ b/src/NHibernate.Test/TransactionTest/Person.hbm.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/NHibernate.Test/TransactionTest/TransactionFixture.cs b/src/NHibernate.Test/TransactionTest/TransactionFixture.cs index 52b7a619c11..204612e1fac 100644 --- a/src/NHibernate.Test/TransactionTest/TransactionFixture.cs +++ b/src/NHibernate.Test/TransactionTest/TransactionFixture.cs @@ -1,19 +1,15 @@ using System; using System.Collections; -using System.Data.Common; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.TransactionTest { [TestFixture] - public class TransactionFixture : TestCase + public class TransactionFixture : TransactionFixtureBase { - protected override IList Mappings - { - // The mapping is only actually needed in one test - get { return new string[] {"Simple.hbm.xml"}; } - } - [Test] public void SecondTransactionShouldntBeCommitted() { @@ -74,25 +70,25 @@ public void CommandAfterTransactionShouldWork() { using (ISession s = OpenSession()) { - using (ITransaction t = s.BeginTransaction()) + using (s.BeginTransaction()) { } - s.CreateQuery("from Simple").List(); + s.CreateQuery("from Person").List(); using (ITransaction t = s.BeginTransaction()) { t.Commit(); } - s.CreateQuery("from Simple").List(); + s.CreateQuery("from Person").List(); using (ITransaction t = s.BeginTransaction()) { t.Rollback(); } - s.CreateQuery("from Simple").List(); + s.CreateQuery("from Person").List(); } } @@ -141,5 +137,40 @@ public void WasCommittedOrRolledBack() } } } + + [Test] + public void FlushFromTransactionAppliesToSharingSession() + { + var flushOrder = new List(); + using (var s = OpenSession(new TestInterceptor(0, flushOrder))) + { + var builder = s.SessionWithOptions().Connection(); + + using (var s1 = builder.Interceptor(new TestInterceptor(1, flushOrder)).OpenSession()) + using (var s2 = builder.Interceptor(new TestInterceptor(2, flushOrder)).OpenSession()) + using (var s3 = s1.SessionWithOptions().Connection().Interceptor(new TestInterceptor(3, flushOrder)).OpenSession()) + using (var t = s.BeginTransaction()) + { + var p1 = new Person(); + var p2 = new Person(); + var p3 = new Person(); + var p4 = new Person(); + s1.Save(p1); + s2.Save(p2); + s3.Save(p3); + s.Save(p4); + t.Commit(); + } + } + + Assert.That(flushOrder, Is.EqualTo(new[] { 1, 2, 3, 0 })); + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(s.Query().Count(), Is.EqualTo(4)); + t.Commit(); + } + } } } \ No newline at end of file diff --git a/src/NHibernate.Test/TransactionTest/TransactionFixtureBase.cs b/src/NHibernate.Test/TransactionTest/TransactionFixtureBase.cs new file mode 100644 index 00000000000..2ad374951cd --- /dev/null +++ b/src/NHibernate.Test/TransactionTest/TransactionFixtureBase.cs @@ -0,0 +1,39 @@ +using System.Collections; +using System.Collections.Generic; + +namespace NHibernate.Test.TransactionTest +{ + public abstract class TransactionFixtureBase : TestCase + { + protected override IList Mappings => new[] { "TransactionTest.Person.hbm.xml" }; + + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from System.Object"); + t.Commit(); + } + } + + public class TestInterceptor : EmptyInterceptor + { + private readonly int _numero; + private readonly List _flushOrder; + + public TestInterceptor(int numero, List flushOrder) + { + _numero = numero; + _flushOrder = flushOrder; + } + + public override void PreFlush(ICollection entitites) + { + _flushOrder.Add(_numero); + } + } + } +} \ No newline at end of file diff --git a/src/NHibernate/AdoNet/ConnectionManager.cs b/src/NHibernate/AdoNet/ConnectionManager.cs index 78274014e0f..258fc4aa7fe 100644 --- a/src/NHibernate/AdoNet/ConnectionManager.cs +++ b/src/NHibernate/AdoNet/ConnectionManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Runtime.Serialization; @@ -41,6 +42,17 @@ public interface Callback private readonly ISessionImplementor session; private readonly ConnectionReleaseMode connectionReleaseMode; private readonly IInterceptor interceptor; + private readonly List _dependentSessions = new List(); + + /// + /// The session responsible for the lifecycle of the connection manager. + /// + public ISessionImplementor Session => session; + + /// + /// The sessions using the connection manager of the session responsible for it. + /// + public IReadOnlyCollection DependentSessions => _dependentSessions; [NonSerialized] private bool _releasesEnabled = true; @@ -63,6 +75,16 @@ public ConnectionManager( ownConnection = suppliedConnection == null; } + public void AddDependentSession(ISessionImplementor session) + { + _dependentSessions.Add(session); + } + + public void RemoveDependentSession(ISessionImplementor session) + { + _dependentSessions.Remove(session); + } + public bool IsInActiveTransaction { get diff --git a/src/NHibernate/ISharedSessionBuilder.cs b/src/NHibernate/ISharedSessionBuilder.cs index 9bbbddbc6cd..964cf9464a7 100644 --- a/src/NHibernate/ISharedSessionBuilder.cs +++ b/src/NHibernate/ISharedSessionBuilder.cs @@ -9,6 +9,8 @@ public interface ISharedSessionBuilder : ISessionBuilder { /// /// Signifies that the connection from the original session should be used to create the new session. + /// The original session remains responsible for it and its closing will cause sharing sessions to be no + /// more usable. /// /// , for method chaining. ISharedSessionBuilder Connection(); diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index bc0704ceebc..0c286635e9a 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -201,6 +201,7 @@ internal SessionImpl(SessionFactoryImpl factory, ISessionCreationOptions options if (options.UserSuppliedConnection != null) throw new SessionException("Cannot simultaneously share transaction context and specify connection"); connectionManager = sharedOptions.ConnectionManager; + connectionManager.AddDependentSession(this); } else { @@ -298,8 +299,8 @@ public DbConnection Close() { if (!_transactionCoordinatorShared) return connectionManager.Close(); - else - return null; + connectionManager.RemoveDependentSession(this); + return null; } finally { @@ -316,6 +317,12 @@ public DbConnection Close() /// public override void AfterTransactionCompletion(bool success, ITransaction tx) { + if (!_transactionCoordinatorShared) + foreach (var dependentSession in ConnectionManager.DependentSessions) + { + dependentSession.AfterTransactionCompletion(success, tx); + } + using (new SessionIdLoggingContext(SessionId)) { log.Debug("transaction completion"); @@ -324,10 +331,15 @@ public override void AfterTransactionCompletion(bool success, ITransaction tx) Factory.StatisticsImplementor.EndTransaction(success); } - connectionManager.AfterTransaction(); + // Let the originating session notify the connection manager + if (!_transactionCoordinatorShared) + { + connectionManager.AfterTransaction(); + } + persistenceContext.AfterTransactionCompletion(); actionQueue.AfterTransactionCompletion(success); - if (!_transactionCoordinatorShared) + if (!_transactionCoordinatorShared || Interceptor != ConnectionManager.Session.Interceptor) { try { @@ -2092,17 +2104,33 @@ public override void AfterTransactionBegin(ITransaction tx) using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - Interceptor.AfterTransactionBegin(tx); + + if (!_transactionCoordinatorShared || Interceptor != ConnectionManager.Session.Interceptor) + { + Interceptor.AfterTransactionBegin(tx); + } } + + if (!_transactionCoordinatorShared) + foreach (var dependentSession in ConnectionManager.DependentSessions) + { + dependentSession.AfterTransactionBegin(tx); + } } public override void BeforeTransactionCompletion(ITransaction tx) { + if (!_transactionCoordinatorShared) + foreach (var dependentSession in ConnectionManager.DependentSessions) + { + dependentSession.BeforeTransactionCompletion(tx); + } + using (new SessionIdLoggingContext(SessionId)) { log.Debug("before transaction completion"); actionQueue.BeforeTransactionCompletion(); - if (!_transactionCoordinatorShared) + if (!_transactionCoordinatorShared || Interceptor != ConnectionManager.Session.Interceptor) { try { diff --git a/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs index d2cdceb3c2c..e79625ac509 100644 --- a/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Linq; using System.Transactions; using NHibernate.Engine; using NHibernate.Engine.Transaction; @@ -29,7 +30,18 @@ public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session) if (System.Transactions.Transaction.Current == null) return; - + + var originatingSession = session.ConnectionManager.Session; + if (originatingSession != session) + { + session.TransactionContext = new DependentContext(); + } + + if (originatingSession.TransactionContext != null) + return; + + session = originatingSession; + var transactionContext = new DistributedTransactionContext(session, System.Transactions.Transaction.Current); session.TransactionContext = transactionContext; @@ -56,11 +68,7 @@ public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session) logger.Warn("Completed transaction was disposed, assuming transaction rollback", ode); } session.AfterTransactionCompletion(wasSuccessful, null); - if (transactionContext.ShouldCloseSessionOnDistributedTransactionCompleted) - { - session.CloseSessionFromDistributedTransaction(); - } - session.TransactionContext = null; + Cleanup(session); } e.Transaction.TransactionCompleted -= handler; @@ -72,9 +80,27 @@ public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session) EnlistmentOptions.EnlistDuringPrepareRequired); } + private static void Cleanup(ISessionImplementor session) + { + foreach (var dependentSession in session.ConnectionManager.DependentSessions.ToList()) + { + if (dependentSession.TransactionContext?.ShouldCloseSessionOnDistributedTransactionCompleted ?? false) + // This change the enumerated collection. + dependentSession.CloseSessionFromDistributedTransaction(); + dependentSession.TransactionContext?.Dispose(); + dependentSession.TransactionContext = null; + } + if (session.TransactionContext.ShouldCloseSessionOnDistributedTransactionCompleted) + { + session.CloseSessionFromDistributedTransaction(); + } + session.TransactionContext.Dispose(); + session.TransactionContext = null; + } + public bool IsInDistributedActiveTransaction(ISessionImplementor session) { - var distributedTransactionContext = ((DistributedTransactionContext)session.TransactionContext); + var distributedTransactionContext = (DistributedTransactionContext)session.ConnectionManager.Session.TransactionContext; return distributedTransactionContext != null && distributedTransactionContext.IsInActiveTransaction; } @@ -115,12 +141,23 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) using (var tx = new TransactionScope(AmbientTransation)) { sessionImplementor.BeforeTransactionCompletion(null); - if (sessionImplementor.FlushMode != FlushMode.Manual && sessionImplementor.ConnectionManager.IsConnected) + if (sessionImplementor.ConnectionManager.IsConnected) { using (sessionImplementor.ConnectionManager.FlushingFromDtcTransaction) { - logger.Debug(string.Format("[session-id={0}] Flushing from Dtc Transaction", sessionImplementor.SessionId)); - sessionImplementor.Flush(); + foreach (var dependentSession in sessionImplementor.ConnectionManager.DependentSessions) + { + if (dependentSession.FlushMode != FlushMode.Manual) + { + logger.DebugFormat("[session-id={0}] Flushing from Dtc Transaction", dependentSession.SessionId); + dependentSession.Flush(); + } + } + if (sessionImplementor.FlushMode != FlushMode.Manual) + { + logger.DebugFormat("[session-id={0}] Flushing from Dtc Transaction", sessionImplementor.SessionId); + sessionImplementor.Flush(); + } } } logger.Debug("prepared for DTC transaction"); @@ -180,5 +217,12 @@ public void Dispose() AmbientTransation.Dispose(); } } + + public class DependentContext : ITransactionContext + { + public bool ShouldCloseSessionOnDistributedTransactionCompleted { get; set; } + + public void Dispose() { } + } } } \ No newline at end of file diff --git a/src/NHibernate/Transaction/AdoTransaction.cs b/src/NHibernate/Transaction/AdoTransaction.cs index 162cba606e1..77902e54f15 100644 --- a/src/NHibernate/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Transaction/AdoTransaction.cs @@ -186,6 +186,13 @@ public void Commit() log.Debug("Start Commit"); + foreach (var dependentSession in session.ConnectionManager.DependentSessions) + { + if (dependentSession.FlushMode != FlushMode.Manual) + { + dependentSession.Flush(); + } + } if (session.FlushMode != FlushMode.Manual) { session.Flush(); From ea9c39ab6e809c716a03b6d18fa979769e658828 Mon Sep 17 00:00:00 2001 From: Alexander Zaytsev Date: Tue, 27 Jun 2017 21:41:18 +1200 Subject: [PATCH 2/2] NH-4034 - realigning transaction management with Hibernate * Hibernate calls statistics between action queue and interceptor * Hibernate calls children AfterCompletion at the end. * Hibernate calls ConnectionManager.AfterTransaction outside of a SessionImpl.AfterTransactionCompletion * Call dependant sessions AfterTransactionCompletion * Flush dependent sessions * Hibernate does not call AfterTransactionBegin for dependent transactions (but should) * Hibernate does not restrict calling interceptors on dependant sessions * Move flush into beforeTransactionCompetion method * Add call to AfterTransactionBegin on dependent sessions * Move using(sessionImplementor.ConnectionManager.FlushingFromDtcTransaction) outside of a loop. --- .../SystemTransactions/TransactionFixture.cs | 4 +- .../TransactionTest/TransactionFixture.cs | 2 +- src/NHibernate/Engine/ISessionImplementor.cs | 5 ++ src/NHibernate/Impl/AbstractSessionImpl.cs | 2 + src/NHibernate/Impl/SessionImpl.cs | 75 +++++++------------ src/NHibernate/Impl/StatelessSessionImpl.cs | 15 +++- ...AdoNetWithDistributedTransactionFactory.cs | 33 ++++---- src/NHibernate/Transaction/AdoTransaction.cs | 22 +++--- 8 files changed, 73 insertions(+), 85 deletions(-) diff --git a/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs b/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs index 86aabd4d3e0..de5dd5eb7c6 100644 --- a/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs +++ b/src/NHibernate.Test/SystemTransactions/TransactionFixture.cs @@ -61,7 +61,7 @@ public void FlushFromTransactionAppliesToDisposedSharingSession() } } - Assert.That(flushOrder, Is.EqualTo(new[] { 1, 2, 3, 0 })); + Assert.That(flushOrder, Is.EqualTo(new[] { 0, 1, 2, 3 })); using (var s = OpenSession()) using (var t = s.BeginTransaction()) @@ -96,7 +96,7 @@ public void FlushFromTransactionAppliesToSharingSession() } } - Assert.That(flushOrder, Is.EqualTo(new[] { 1, 2, 3, 0 })); + Assert.That(flushOrder, Is.EqualTo(new[] { 0, 1, 2, 3 })); using (var s = OpenSession()) using (var t = s.BeginTransaction()) diff --git a/src/NHibernate.Test/TransactionTest/TransactionFixture.cs b/src/NHibernate.Test/TransactionTest/TransactionFixture.cs index 204612e1fac..3c3d2e54a09 100644 --- a/src/NHibernate.Test/TransactionTest/TransactionFixture.cs +++ b/src/NHibernate.Test/TransactionTest/TransactionFixture.cs @@ -163,7 +163,7 @@ public void FlushFromTransactionAppliesToSharingSession() } } - Assert.That(flushOrder, Is.EqualTo(new[] { 1, 2, 3, 0 })); + Assert.That(flushOrder, Is.EqualTo(new[] { 0, 1, 2, 3 })); using (var s = OpenSession()) using (var t = s.BeginTransaction()) diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index e93c9082035..c59be757f01 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -186,6 +186,11 @@ public interface ISessionImplementor /// void BeforeTransactionCompletion(ITransaction tx); + /// + /// + /// + void FlushBeforeTransactionCompletion(); + /// /// Notify the session that the transaction completed, so we no longer own the old locks. /// (Also we should release cache softlocks). May be called multiple times during the transaction diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 9c6e844fa62..cc1363f461c 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -152,6 +152,7 @@ public virtual IList List(CriteriaImpl criteria) public abstract IEntityPersister GetEntityPersister(string entityName, object obj); public abstract void AfterTransactionBegin(ITransaction tx); public abstract void BeforeTransactionCompletion(ITransaction tx); + public abstract void FlushBeforeTransactionCompletion(); public abstract void AfterTransactionCompletion(bool successful, ITransaction tx); public abstract object GetContextEntityIdentifier(object obj); public abstract object Instantiate(string clazz, object id); @@ -430,6 +431,7 @@ protected void AfterOperation(bool success) if (!ConnectionManager.IsInActiveTransaction) { ConnectionManager.AfterNonTransactionalQuery(success); + ConnectionManager.AfterTransaction(); AfterTransactionCompletion(success, null); } } diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index 0c286635e9a..a6e86c5d984 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -317,41 +317,27 @@ public DbConnection Close() /// public override void AfterTransactionCompletion(bool success, ITransaction tx) { - if (!_transactionCoordinatorShared) - foreach (var dependentSession in ConnectionManager.DependentSessions) - { - dependentSession.AfterTransactionCompletion(success, tx); - } - using (new SessionIdLoggingContext(SessionId)) { log.Debug("transaction completion"); + + persistenceContext.AfterTransactionCompletion(); + actionQueue.AfterTransactionCompletion(success); + if (Factory.Statistics.IsStatisticsEnabled) { Factory.StatisticsImplementor.EndTransaction(success); } - // Let the originating session notify the connection manager - if (!_transactionCoordinatorShared) + try { - connectionManager.AfterTransaction(); + Interceptor.AfterTransactionCompletion(tx); } - - persistenceContext.AfterTransactionCompletion(); - actionQueue.AfterTransactionCompletion(success); - if (!_transactionCoordinatorShared || Interceptor != ConnectionManager.Session.Interceptor) + catch (Exception t) { - try - { - Interceptor.AfterTransactionCompletion(tx); - } - catch (Exception t) - { - log.Error("exception in interceptor afterTransactionCompletion()", t); - } + log.Error("exception in interceptor afterTransactionCompletion()", t); } - //if (autoClear) // Clear(); } @@ -2104,48 +2090,39 @@ public override void AfterTransactionBegin(ITransaction tx) using (new SessionIdLoggingContext(SessionId)) { CheckAndUpdateSessionStatus(); - - if (!_transactionCoordinatorShared || Interceptor != ConnectionManager.Session.Interceptor) - { - Interceptor.AfterTransactionBegin(tx); - } + Interceptor.AfterTransactionBegin(tx); } - - if (!_transactionCoordinatorShared) - foreach (var dependentSession in ConnectionManager.DependentSessions) - { - dependentSession.AfterTransactionBegin(tx); - } } public override void BeforeTransactionCompletion(ITransaction tx) { - if (!_transactionCoordinatorShared) - foreach (var dependentSession in ConnectionManager.DependentSessions) - { - dependentSession.BeforeTransactionCompletion(tx); - } - using (new SessionIdLoggingContext(SessionId)) { log.Debug("before transaction completion"); + FlushBeforeTransactionCompletion(); actionQueue.BeforeTransactionCompletion(); - if (!_transactionCoordinatorShared || Interceptor != ConnectionManager.Session.Interceptor) + try { - try - { - Interceptor.BeforeTransactionCompletion(tx); - } - catch (Exception e) - { - log.Error("exception in interceptor BeforeTransactionCompletion()", e); + Interceptor.BeforeTransactionCompletion(tx); + } + catch (Exception e) + { + log.Error("exception in interceptor BeforeTransactionCompletion()", e); - throw; - } + throw; } } } + public override void FlushBeforeTransactionCompletion() + { + using (new SessionIdLoggingContext(SessionId)) + { + if (FlushMode != FlushMode.Manual) + Flush(); + } + } + public ISession SetBatchSize(int batchSize) { Batcher.BatchSize = batchSize; diff --git a/src/NHibernate/Impl/StatelessSessionImpl.cs b/src/NHibernate/Impl/StatelessSessionImpl.cs index 6933c4e3a82..c0f5565ab74 100644 --- a/src/NHibernate/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Impl/StatelessSessionImpl.cs @@ -27,8 +27,10 @@ namespace NHibernate.Impl public class StatelessSessionImpl : AbstractSessionImpl, IStatelessSession { private static readonly IInternalLogger log = LoggerProvider.LoggerFor(typeof(StatelessSessionImpl)); + [NonSerialized] private readonly ConnectionManager connectionManager; + [NonSerialized] private readonly StatefulPersistenceContext temporaryPersistenceContext; @@ -179,7 +181,7 @@ public override void List(CriteriaImpl criteria, IList results) temporaryPersistenceContext.Clear(); } } - + public override IEnumerable Enumerable(IQueryExpression queryExpression, QueryParameters queryParameters) { throw new NotImplementedException(); @@ -216,16 +218,22 @@ public override void AfterTransactionBegin(ITransaction tx) public override void BeforeTransactionCompletion(ITransaction tx) { + FlushBeforeTransactionCompletion(); } - public override void AfterTransactionCompletion(bool successful, ITransaction tx) + public override void FlushBeforeTransactionCompletion() { using (new SessionIdLoggingContext(SessionId)) { - connectionManager.AfterTransaction(); + if (FlushMode != FlushMode.Manual) + Flush(); } } + public override void AfterTransactionCompletion(bool successful, ITransaction tx) + { + } + public override object GetContextEntityIdentifier(object obj) { CheckAndUpdateSessionStatus(); @@ -838,6 +846,7 @@ public ITransaction BeginTransaction(IsolationLevel isolationLevel) #endregion #region IDisposable Members + private bool _isAlreadyDisposed; /// diff --git a/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs index e79625ac509..6b7fc7ab64b 100644 --- a/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs @@ -48,6 +48,8 @@ public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session) logger.DebugFormat("enlisted into DTC transaction: {0}", transactionContext.AmbientTransation.IsolationLevel); session.AfterTransactionBegin(null); + foreach (var dependentSession in session.ConnectionManager.DependentSessions) + dependentSession.AfterTransactionBegin(null); TransactionCompletedEventHandler handler = null; @@ -67,7 +69,11 @@ public void EnlistInDistributedTransactionIfNeeded(ISessionImplementor session) { logger.Warn("Completed transaction was disposed, assuming transaction rollback", ode); } + session.ConnectionManager.AfterTransaction(); session.AfterTransactionCompletion(wasSuccessful, null); + foreach (var dependentSession in session.ConnectionManager.DependentSessions) + dependentSession.AfterTransactionCompletion(wasSuccessful, null); + Cleanup(session); } @@ -140,29 +146,19 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) { using (var tx = new TransactionScope(AmbientTransation)) { - sessionImplementor.BeforeTransactionCompletion(null); if (sessionImplementor.ConnectionManager.IsConnected) { using (sessionImplementor.ConnectionManager.FlushingFromDtcTransaction) { + sessionImplementor.BeforeTransactionCompletion(null); foreach (var dependentSession in sessionImplementor.ConnectionManager.DependentSessions) - { - if (dependentSession.FlushMode != FlushMode.Manual) - { - logger.DebugFormat("[session-id={0}] Flushing from Dtc Transaction", dependentSession.SessionId); - dependentSession.Flush(); - } - } - if (sessionImplementor.FlushMode != FlushMode.Manual) - { - logger.DebugFormat("[session-id={0}] Flushing from Dtc Transaction", sessionImplementor.SessionId); - sessionImplementor.Flush(); - } + dependentSession.BeforeTransactionCompletion(null); + + logger.Debug("prepared for DTC transaction"); + + tx.Complete(); } } - logger.Debug("prepared for DTC transaction"); - - tx.Complete(); } preparingEnlistment.Prepared(); } @@ -202,7 +198,10 @@ void IEnlistmentNotification.InDoubt(Enlistment enlistment) { using (new SessionIdLoggingContext(sessionImplementor.SessionId)) { + sessionImplementor.ConnectionManager.AfterTransaction(); sessionImplementor.AfterTransactionCompletion(false, null); + foreach (var dependentSession in sessionImplementor.ConnectionManager.DependentSessions) + dependentSession.AfterTransactionCompletion(false, null); logger.Debug("DTC transaction is in doubt"); enlistment.Done(); IsInActiveTransaction = false; @@ -225,4 +224,4 @@ public class DependentContext : ITransactionContext public void Dispose() { } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Transaction/AdoTransaction.cs b/src/NHibernate/Transaction/AdoTransaction.cs index 77902e54f15..3b3f79ac5d6 100644 --- a/src/NHibernate/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Transaction/AdoTransaction.cs @@ -154,6 +154,8 @@ public void Begin(IsolationLevel isolationLevel) rolledBack = false; session.AfterTransactionBegin(this); + foreach (var dependentSession in session.ConnectionManager.DependentSessions) + dependentSession.AfterTransactionBegin(this); } } @@ -161,8 +163,12 @@ private void AfterTransactionCompletion(bool successful) { using (new SessionIdLoggingContext(sessionId)) { + session.ConnectionManager.AfterTransaction(); session.AfterTransactionCompletion(successful, this); NotifyLocalSynchsAfterTransactionCompletion(successful); + foreach (var dependentSession in session.ConnectionManager.DependentSessions) + dependentSession.AfterTransactionCompletion(successful, this); + session = null; begun = false; } @@ -186,20 +192,10 @@ public void Commit() log.Debug("Start Commit"); - foreach (var dependentSession in session.ConnectionManager.DependentSessions) - { - if (dependentSession.FlushMode != FlushMode.Manual) - { - dependentSession.Flush(); - } - } - if (session.FlushMode != FlushMode.Manual) - { - session.Flush(); - } - - NotifyLocalSynchsBeforeTransactionCompletion(); session.BeforeTransactionCompletion(this); + NotifyLocalSynchsBeforeTransactionCompletion(); + foreach (var dependentSession in session.ConnectionManager.DependentSessions) + dependentSession.BeforeTransactionCompletion(this); try {