Skip to content

Commit 8e343f6

Browse files
NH-4031 - adding AsyncLocalSessionContext.
1 parent 878ae7b commit 8e343f6

File tree

6 files changed

+126
-13
lines changed

6 files changed

+126
-13
lines changed

doc/reference/modules/architecture.xml

+23-13
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,21 @@
239239
</para>
240240

241241
<itemizedlist>
242+
<listitem>
243+
<para>
244+
<literal>NHibernate.Context.AsyncLocalSessionContext</literal> - current sessions are tracked
245+
by current asynchronous flow. You are responsible to bind and unbind an
246+
<literal>ISession</literal> instance with static methods of class
247+
<literal>CurrentSessionContext</literal>. Binding operations from inner flows will not be
248+
propagated to outer or siblings flows.
249+
</para>
250+
</listitem>
242251
<listitem>
243252
<para>
244253
<literal>NHibernate.Context.CallSessionContext</literal> - current sessions are tracked
245-
by <literal>CallContext</literal>. You are responsible to bind and unbind an <literal>
246-
ISession</literal> instance with static methods of class <literal>CurrentSessionContext
247-
</literal>.
254+
by <literal>CallContext</literal>. You are responsible to bind and unbind an
255+
<literal>ISession</literal> instance with static methods of class
256+
<literal>CurrentSessionContext</literal>.
248257
</para>
249258
</listitem>
250259
<listitem>
@@ -257,26 +266,26 @@
257266
</listitem>
258267
<listitem>
259268
<para>
260-
<literal>NHibernate.Context.WebSessionContext</literal> -
261-
stores the current session in <literal>HttpContext</literal>.
262-
You are responsible to bind and unbind an <literal>ISession</literal>
263-
instance with static methods of class <literal>CurrentSessionContext</literal>.
269+
<literal>NHibernate.Context.WebSessionContext</literal> -
270+
stores the current session in <literal>HttpContext</literal>.
271+
You are responsible to bind and unbind an <literal>ISession</literal>
272+
instance with static methods of class <literal>CurrentSessionContext</literal>.
264273
</para>
265274
</listitem>
266275
<listitem>
267276
<para>
268277
<literal>NHibernate.Context.WcfOperationSessionContext</literal> - current sessions are tracked
269278
by WCF <literal>OperationContext</literal>. You need to register the <literal>WcfStateExtension</literal>
270-
extension in WCF. You are responsible to bind and unbind an <literal>ISession
271-
</literal> instance with static methods of class <literal>CurrentSessionContext</literal>.
279+
extension in WCF. You are responsible to bind and unbind an <literal>ISession</literal>
280+
instance with static methods of class <literal>CurrentSessionContext</literal>.
272281
</para>
273282
</listitem>
274283
<listitem>
275284
<para>
276285
<literal>NHibernate.Context.ManagedWebSessionContext</literal> - current
277-
sessions are tracked by <literal>HttpContext</literal>. Removed in NHibernate 4.0
278-
- <literal>NHibernate.Context.WebSessionContext</literal> should be used instead.
279-
You are responsible to bind and unbind an <literal>ISession</literal> instance with static methods
286+
sessions are tracked by <literal>HttpContext</literal>. Removed in NHibernate 4.0
287+
- <literal>NHibernate.Context.WebSessionContext</literal> should be used instead.
288+
You are responsible to bind and unbind an <literal>ISession</literal> instance with static methods
280289
on this class, it never opens, flushes, or closes an <literal>ISession</literal> itself.
281290
</para>
282291
</listitem>
@@ -287,7 +296,8 @@
287296
defines which <literal>NHibernate.Context.ICurrentSessionContext</literal> implementation
288297
should be used. Typically, the value of this parameter would just name the implementation
289298
class to use (including the assembly name); for the out-of-the-box implementations, however,
290-
there are corresponding short names: "call", "thread_static", "web" and "wcf_operation",
299+
there are corresponding short names: <literal>async_local</literal>, <literal>call</literal>,
300+
<literal>thread_static</literal>, <literal>web</literal> and <literal>wcf_operation</literal>,
291301
respectively.
292302
</para>
293303
</sect1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Threading.Tasks;
2+
using NHibernate.Cfg;
3+
using NHibernate.Context;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.ConnectionTest
7+
{
8+
[TestFixture]
9+
public class AsyncLocalSessionContextFixture : ConnectionManagementTestCase
10+
{
11+
protected override ISession GetSessionUnderTest()
12+
{
13+
var session = OpenSession();
14+
session.BeginTransaction();
15+
return session;
16+
}
17+
18+
protected override void Configure(Configuration configuration)
19+
{
20+
base.Configure(cfg);
21+
cfg.SetProperty(Environment.CurrentSessionContextClass, "async_local");
22+
}
23+
24+
[Test]
25+
public async Task AsyncLocalIsolation()
26+
{
27+
using (var session = OpenSession())
28+
{
29+
CurrentSessionContext.Bind(session);
30+
AssertCurrentSession(session, "Unexpected session after outer bind.");
31+
32+
await SubBind(session);
33+
AssertCurrentSession(session, "Unexpected session after end of SubBind call.");
34+
}
35+
}
36+
37+
private async Task SubBind(ISession firstSession)
38+
{
39+
AssertCurrentSession(firstSession, "Unexpected session at start of SubBind.");
40+
41+
using (var session = OpenSession())
42+
{
43+
CurrentSessionContext.Bind(session);
44+
AssertCurrentSession(session, "Unexpected session after inner bind.");
45+
46+
await Dummy();
47+
AssertCurrentSession(session, "Unexpected session after dummy await.");
48+
}
49+
}
50+
51+
private Task Dummy()
52+
{
53+
return Task.FromResult(0);
54+
}
55+
56+
private void AssertCurrentSession(ISession session, string message)
57+
{
58+
Assert.That(
59+
Sfi.GetCurrentSession(),
60+
Is.EqualTo(session),
61+
"{0} {1} instead of {2}.", message,
62+
Sfi.GetCurrentSession().GetSessionImplementation().SessionId,
63+
session.GetSessionImplementation().SessionId);
64+
}
65+
}
66+
}

src/NHibernate.Test/NHibernate.Test.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
<Compile Include="ConnectionTest\BatcherFixture.cs" />
209209
<Compile Include="ConnectionTest\AggressiveReleaseTest.cs" />
210210
<Compile Include="ConnectionTest\ConnectionManagementTestCase.cs" />
211+
<Compile Include="ConnectionTest\AsyncLocalSessionContextFixture.cs" />
211212
<Compile Include="ConnectionTest\YetAnother.cs" />
212213
<Compile Include="ConnectionTest\Other.cs" />
213214
<Compile Include="ConnectionTest\Silly.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Threading;
3+
using NHibernate.Engine;
4+
5+
namespace NHibernate.Context
6+
{
7+
// Session contextes are serializable while not actually serializing any data. Others contextes just retrieve it back
8+
// from their context, if it does still live when/where they are de-serialized. For having that with AsyncLocal,
9+
// we would need to store it as static, and then we need to use a MapBasedSessionContext.
10+
// But this would cause bindings operations done in inner flows to be potentially propagated to outer flows, depending
11+
// on which flow has initialized the map. This is undesirable.
12+
// So current implementation just loses its context in case of serialization, since AsyncLocal is not serializable.
13+
// Another option would be to change MapBasedSessionContext for recreating a new dictionary from the
14+
// previous one at each change, essentially using those dictionaries as immutable objects.
15+
/// <summary>
16+
/// Provides a <see cref="ISessionFactory.GetCurrentSession()">current session</see>
17+
/// for current asynchronous flow.
18+
/// </summary>
19+
[Serializable]
20+
public class AsyncLocalSessionContext : CurrentSessionContext
21+
{
22+
private readonly AsyncLocal<ISession> _session = new AsyncLocal<ISession>();
23+
24+
// Constructor signature required for dynamic invocation code.
25+
public AsyncLocalSessionContext(ISessionFactoryImplementor factory) { }
26+
27+
protected override ISession Session
28+
{
29+
get => _session.Value;
30+
set => _session.Value = value;
31+
}
32+
}
33+
}

src/NHibernate/Impl/SessionFactoryImpl.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,8 @@ private ICurrentSessionContext BuildCurrentSessionContext()
12231223
{
12241224
case null:
12251225
return null;
1226+
case "async_local":
1227+
return new AsyncLocalSessionContext(this);
12261228
case "call":
12271229
return new CallSessionContext(this);
12281230
case "thread_static":

src/NHibernate/NHibernate.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
<Compile Include="Connection\DriverConnectionProvider.cs" />
145145
<Compile Include="Connection\IConnectionProvider.cs" />
146146
<Compile Include="Connection\UserSuppliedConnectionProvider.cs" />
147+
<Compile Include="Context\AsyncLocalSessionContext.cs" />
147148
<Compile Include="Dialect\PostgreSQL83Dialect.cs" />
148149
<Compile Include="Impl\ISharedSessionCreationOptions.cs" />
149150
<Compile Include="ISharedSessionBuilder.cs" />

0 commit comments

Comments
 (0)