diff --git a/src/NHibernate.Test/NHSpecificTest/GH1594/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH1594/Entity.cs new file mode 100644 index 00000000000..676575207f3 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1594/Entity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH1594 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs b/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs new file mode 100644 index 00000000000..efc3f52831e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1594/ExecutionContextExtensions.cs @@ -0,0 +1,20 @@ +using System.Collections; +using System.Threading; + +namespace NHibernate.Test.NHSpecificTest.GH1594 +{ + public static class ExecutionContextExtensions + { + public static int LocalValuesCount(this ExecutionContext c) + { +#if NETFX + const string localValuesFieldName = "_localValues"; +#else + const string localValuesFieldName = "m_localValues"; +#endif + var f = typeof(ExecutionContext).GetField(localValuesFieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var d = (IDictionary) f.GetValue(c); + return d?.Count ?? 0; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs new file mode 100644 index 00000000000..0f0118cb56a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1594/Fixture.cs @@ -0,0 +1,72 @@ +using System.Linq; +using System.Threading; +using System.Transactions; +using NHibernate.Engine; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1594 +{ + [TestFixture] + public class Fixture : BugTestCase + { + protected override bool AppliesTo(ISessionFactoryImplementor factory) => + factory.ConnectionProvider.Driver.SupportsSystemTransactions; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new GH1594.Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new GH1594.Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // The HQL delete does all the job inside the database without loading the entities, but it does + // not handle delete order for avoiding violating constraints if any. Use + // session.Delete("from System.Object"); + // instead if in need of having NHbernate ordering the deletes, but this will cause + // loading the entities in the session. + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void ExecutionContextLocalValuesLeak() + { + using (var session = OpenSession()) + { + RunInTransaction(session); + var localValuesCountAfterFirstCall = ExecutionContext.Capture().LocalValuesCount(); + RunInTransaction(session); + var localValuesCountAfterSecondCall = ExecutionContext.Capture().LocalValuesCount(); + Assert.AreEqual(localValuesCountAfterFirstCall, localValuesCountAfterSecondCall); + } + } + + private void RunInTransaction(ISession session) + { + using (var ts = new TransactionScope()) + { + var result = from e in session.Query() + where e.Name == "Bob" + select e; + + result.ToList(); + ts.Complete(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1594/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1594/Mappings.hbm.xml new file mode 100644 index 00000000000..021e5ab8b62 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1594/Mappings.hbm.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs b/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs index bf8f594c281..402ac3db5f3 100644 --- a/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs +++ b/src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs @@ -187,7 +187,7 @@ public class SystemTransactionContext : ITransactionContext, IEnlistmentNotifica private readonly ManualResetEventSlim _lock = new ManualResetEventSlim(true); private volatile bool _needCompletionLocking = true; // Required for not locking the completion phase itself when locking session usages from concurrent threads. - private readonly AsyncLocal _bypassLock = new AsyncLocal(); + private static readonly AsyncLocal _bypassLock = new AsyncLocal(); private readonly int _systemTransactionCompletionLockTimeout; /// @@ -269,6 +269,7 @@ protected virtual void Lock() protected virtual void Unlock() { _lock.Set(); + _bypassLock.Value = false; } ///