Skip to content

Commit 058ecce

Browse files
pecanwhazzik
authored andcommitted
Fix AsyncLocal leak in SystemTransactionContext (#1596)
Fixes #1594
1 parent aacd72c commit 058ecce

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH1594
4+
{
5+
class Entity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections;
2+
using System.Threading;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH1594
5+
{
6+
public static class ExecutionContextExtensions
7+
{
8+
public static int LocalValuesCount(this ExecutionContext c)
9+
{
10+
#if NETFX
11+
const string localValuesFieldName = "_localValues";
12+
#else
13+
const string localValuesFieldName = "m_localValues";
14+
#endif
15+
var f = typeof(ExecutionContext).GetField(localValuesFieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
16+
var d = (IDictionary) f.GetValue(c);
17+
return d?.Count ?? 0;
18+
}
19+
}
20+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.Linq;
2+
using System.Threading;
3+
using System.Transactions;
4+
using NHibernate.Engine;
5+
using NUnit.Framework;
6+
7+
namespace NHibernate.Test.NHSpecificTest.GH1594
8+
{
9+
[TestFixture]
10+
public class Fixture : BugTestCase
11+
{
12+
protected override bool AppliesTo(ISessionFactoryImplementor factory) =>
13+
factory.ConnectionProvider.Driver.SupportsSystemTransactions;
14+
15+
protected override void OnSetUp()
16+
{
17+
using (var session = OpenSession())
18+
using (var transaction = session.BeginTransaction())
19+
{
20+
var e1 = new GH1594.Entity {Name = "Bob"};
21+
session.Save(e1);
22+
23+
var e2 = new GH1594.Entity {Name = "Sally"};
24+
session.Save(e2);
25+
26+
transaction.Commit();
27+
}
28+
}
29+
30+
protected override void OnTearDown()
31+
{
32+
using (var session = OpenSession())
33+
using (var transaction = session.BeginTransaction())
34+
{
35+
// The HQL delete does all the job inside the database without loading the entities, but it does
36+
// not handle delete order for avoiding violating constraints if any. Use
37+
// session.Delete("from System.Object");
38+
// instead if in need of having NHbernate ordering the deletes, but this will cause
39+
// loading the entities in the session.
40+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
41+
42+
transaction.Commit();
43+
}
44+
}
45+
46+
[Test]
47+
public void ExecutionContextLocalValuesLeak()
48+
{
49+
using (var session = OpenSession())
50+
{
51+
RunInTransaction(session);
52+
var localValuesCountAfterFirstCall = ExecutionContext.Capture().LocalValuesCount();
53+
RunInTransaction(session);
54+
var localValuesCountAfterSecondCall = ExecutionContext.Capture().LocalValuesCount();
55+
Assert.AreEqual(localValuesCountAfterFirstCall, localValuesCountAfterSecondCall);
56+
}
57+
}
58+
59+
private void RunInTransaction(ISession session)
60+
{
61+
using (var ts = new TransactionScope())
62+
{
63+
var result = from e in session.Query<GH1594.Entity>()
64+
where e.Name == "Bob"
65+
select e;
66+
67+
result.ToList();
68+
ts.Complete();
69+
}
70+
}
71+
}
72+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.GH1594">
4+
5+
<class name="Entity">
6+
<id name="Id" generator="guid.comb"/>
7+
<property name="Name"/>
8+
</class>
9+
10+
</hibernate-mapping>

src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public class SystemTransactionContext : ITransactionContext, IEnlistmentNotifica
187187
private readonly ManualResetEventSlim _lock = new ManualResetEventSlim(true);
188188
private volatile bool _needCompletionLocking = true;
189189
// Required for not locking the completion phase itself when locking session usages from concurrent threads.
190-
private readonly AsyncLocal<bool> _bypassLock = new AsyncLocal<bool>();
190+
private static readonly AsyncLocal<bool> _bypassLock = new AsyncLocal<bool>();
191191
private readonly int _systemTransactionCompletionLockTimeout;
192192

193193
/// <summary>
@@ -269,6 +269,7 @@ protected virtual void Lock()
269269
protected virtual void Unlock()
270270
{
271271
_lock.Set();
272+
_bypassLock.Value = false;
272273
}
273274

274275
/// <summary>

0 commit comments

Comments
 (0)