Skip to content

Commit a379e17

Browse files
Merge branch 'master' into NH-3787
2 parents b3fe64b + 5c4eccb commit a379e17

37 files changed

+1067
-830
lines changed

GitReleaseManager.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ issue-labels-include:
66
- "t: Improvement"
77
- "t: Task"
88
issue-labels-exclude:
9+
- "r: Duplicate"
10+
- "r: External Issue"
11+
- "r: Not an Issue"
12+
- "r: Obsolete"
913
- "r: Rejected"
1014
- "r: Replaced"
15+
- "t: Fix"
1116
issue-labels-alias:
1217
- name: "t: Bug"
1318
header: Bug

build-common/NHibernate.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<VersionMajor Condition="'$(VersionMajor)' == ''">5</VersionMajor>
44
<VersionMinor Condition="'$(VersionMinor)' == ''">0</VersionMinor>
5-
<VersionPatch Condition="'$(VersionPatch)' == ''">1</VersionPatch>
5+
<VersionPatch Condition="'$(VersionPatch)' == ''">2</VersionPatch>
66
<VersionSuffix Condition="'$(VersionSuffix)' == ''"></VersionSuffix>
77

88
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>

build-common/common.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030

3131
<!-- This is used only for build folder -->
3232
<!-- TODO: Either remove or refactor to use NHibernate.props -->
33-
<property name="project.version" value="5.0.1" overwrite="false" />
34-
<property name="project.version.numeric" value="5.0.1" overwrite="false" />
33+
<property name="project.version" value="5.0.2" overwrite="false" />
34+
<property name="project.version.numeric" value="5.0.2" overwrite="false" />
3535

3636
<!-- properties used to connect to database for testing -->
3737
<include buildfile="nhibernate-properties.xml" />

releasenotes.txt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1-
Build 5.0.1
1+
Build 5.0.2
2+
=============================
3+
4+
Release notes - NHibernate - Version 5.0.2
5+
6+
** Bug
7+
* #1456 NH-4052 - Add missing serializable implementation
8+
* #1455 Reduces check session and set context id redundant calls
9+
* #1453 Eliminate unnecessary AsyncLocal allocation if SessionId isn't changed
10+
11+
** Task
12+
* #1457 Release 5.0.2
13+
14+
As part of releasing 5.0.2, a missing 5.0.0 possible breaking change has been added about Dialects requiring now
15+
to be configured. See 5.0.0 possible breaking changes.
16+
17+
Build 5.0.1
218
=============================
319

420
Release notes - NHibernate - Version 5.0.1
@@ -85,6 +101,9 @@ Build 5.0.0
85101
the dialect. They resolve to 4000 length string and (28, 10) precision/scale decimals by default, and are
86102
trimmed down according to dialect. Those defaults can be overridden with query.default_cast_length,
87103
query.default_cast_precision and query.default_cast_scale settings.
104+
* Dialects are now configurable. If you instantiate a dialect directly, make sure you call its Configure
105+
method, with as argument the properties of a NHibernate Configuration object. You may use instead
106+
Dialect.GetDialect methods, which configure the dialect before returning it.
88107
* Transaction scopes handling has undergone a major rework. See NH-4011 for full details.
89108
** More transaction promotion to distributed may occur if you use the "flush on commit" feature with
90109
transaction scopes. Explicitly flush your session instead. Ensure it does not occur by disabling
@@ -156,9 +175,6 @@ Release notes - NHibernate - Version 5.0.0
156175
* [NH-3957] - Second level query cache may yields wrong cache entry
157176
* [NH-4001] - Remove ThreadSafeDictionary
158177

159-
160-
161-
162178
** Bug
163179
* [NH-926] - Identity insert fails with SQL Ce dialect and aggressive connection release mode.
164180
* [NH-1752] - NHibernate Date type converts to NULL

src/NHibernate.Test/Async/NHSpecificTest/Logs/LogsFixture.cs

Lines changed: 182 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@
99

1010

1111
using System.Collections;
12-
12+
using System.Collections.Concurrent;
13+
using System.Linq;
14+
using System.Threading;
15+
using System.Threading.Tasks;
16+
using NHibernate.Cfg;
1317
using NHibernate.Impl;
14-
18+
using NHibernate.SqlCommand;
19+
using NHibernate.Type;
1520
using NUnit.Framework;
21+
using NHibernate.Linq;
1622

1723
namespace NHibernate.Test.NHSpecificTest.Logs
1824
{
@@ -24,7 +30,6 @@ namespace NHibernate.Test.NHSpecificTest.Logs
2430
using log4net.Core;
2531
using log4net.Layout;
2632
using log4net.Repository.Hierarchy;
27-
using System.Threading.Tasks;
2833

2934
[TestFixture]
3035
public class LogsFixtureAsync : TestCase
@@ -39,6 +44,33 @@ protected override string MappingsAssembly
3944
get { return "NHibernate.Test"; }
4045
}
4146

47+
protected override void Configure(Configuration configuration)
48+
{
49+
base.Configure(configuration);
50+
configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false");
51+
}
52+
53+
protected override void OnSetUp()
54+
{
55+
using (var s = Sfi.OpenSession())
56+
using (var t = s.BeginTransaction())
57+
{
58+
s.Save(new Person());
59+
s.Save(new Person());
60+
t.Commit();
61+
}
62+
}
63+
64+
protected override void OnTearDown()
65+
{
66+
using (var s = Sfi.OpenSession())
67+
using (var t = s.BeginTransaction())
68+
{
69+
s.CreateQuery("delete from Person").ExecuteUpdate();
70+
t.Commit();
71+
}
72+
}
73+
4274
[Test]
4375
public async Task WillGetSessionIdFromSessionLogsAsync()
4476
{
@@ -47,15 +79,128 @@ public async Task WillGetSessionIdFromSessionLogsAsync()
4779
using (var spy = new TextLogSpy("NHibernate.SQL", "%message | SessionId: %property{sessionId}"))
4880
using (var s = Sfi.OpenSession())
4981
{
50-
var sessionId = ((SessionImpl)s).SessionId;
82+
var sessionId = ((SessionImpl) s).SessionId;
5183

52-
await (s.GetAsync<Person>(1));//will execute some sql
84+
await (s.GetAsync<Person>(1)); //will execute some sql
5385

5486
var loggingEvent = spy.GetWholeLog();
5587
Assert.That(loggingEvent.Contains(sessionId.ToString()), Is.True);
5688
}
5789
}
5890

91+
[Test]
92+
public async Task WillGetSessionIdFromConsecutiveSessionsLogsAsync()
93+
{
94+
GlobalContext.Properties["sessionId"] = new SessionIdCapturer();
95+
96+
using (var spy = new TextLogSpy("NHibernate.SQL", "%message | SessionId: %property{sessionId}"))
97+
{
98+
var sessions = Enumerable.Range(1, 10).Select(i => Sfi.OpenSession()).ToArray();
99+
try
100+
{
101+
for (var i = 0; i < 10; i++)
102+
for (var j = 0; j < 10; j++)
103+
{
104+
var s = sessions[j];
105+
await (s.GetAsync<Person>(i * 10 + j)); //will execute some sql
106+
}
107+
}
108+
finally
109+
{
110+
foreach (var s in sessions)
111+
{
112+
s.Dispose();
113+
}
114+
}
115+
116+
var loggingEvent = spy.GetWholeLog();
117+
for (var i = 0; i < 10; i++)
118+
for (var j = 0; j < 10; j++)
119+
{
120+
var sessionId = sessions[j].GetSessionImplementation().SessionId;
121+
Assert.That(loggingEvent, Does.Contain($"p0 = {i * 10 + j} [Type: Int32 (0:0:0)] | SessionId: {sessionId}"));
122+
}
123+
}
124+
}
125+
126+
[Test]
127+
public async Task WillGetSessionIdFromInterlacedSessionsLogsAsync()
128+
{
129+
GlobalContext.Properties["sessionId"] = new SessionIdCapturer();
130+
var interceptor = new InterlacedSessionInterceptor(Sfi);
131+
using (var spy = new TextLogSpy("NHibernate.SQL", "%message | SessionId: %property{sessionId}"))
132+
using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession())
133+
{
134+
// Trigger an operation which will fire many interceptor events, before and after s own logging.
135+
var persons = await (s.Query<Person>().ToListAsync());
136+
137+
var loggingEvent = spy.GetWholeLog();
138+
for (var i = 0; i < interceptor.SessionIds.Count; i++)
139+
{
140+
var sessionId = interceptor.SessionIds[i];
141+
Assert.That(loggingEvent, Does.Contain($"p0 = {i + 1} [Type: Int32 (0:0:0)] | SessionId: {sessionId}"));
142+
}
143+
Assert.That(loggingEvent, Does.Contain($"Person person0_ | SessionId: {s.GetSessionImplementation().SessionId}"));
144+
}
145+
}
146+
147+
[Test]
148+
public async Task WillGetSessionIdFromSessionLogsConcurrentAsync()
149+
{
150+
GlobalContext.Properties["sessionId"] = new SessionIdCapturer();
151+
152+
// Do not use a ManualResetEventSlim, it does not support async and exhausts the task thread pool in the
153+
// async counterparts of this test. SemaphoreSlim has the async support and release the thread when waiting.
154+
var semaphore = new SemaphoreSlim(0);
155+
var failures = new ConcurrentBag<Exception>();
156+
var sessionIds = new ConcurrentDictionary<int, Guid>();
157+
using (var spy = new TextLogSpy("NHibernate.SQL", "%message | SessionId: %property{sessionId}"))
158+
{
159+
await (Task.WhenAll(Enumerable.Range( 1, 12).Select( async i =>
160+
{
161+
if (i > 10)
162+
{
163+
// Give some time to threads for reaching the wait, having all of them ready to do most of their job concurrently.
164+
await (Task.Delay(100));
165+
semaphore.Release(10);
166+
return;
167+
}
168+
try
169+
{
170+
using (var s = Sfi.OpenSession())
171+
{
172+
sessionIds.AddOrUpdate(
173+
i,
174+
s.GetSessionImplementation().SessionId,
175+
(ti, old) => throw new InvalidOperationException(
176+
$"Thread number {ti} has already session id {old}, while attempting to set it to" +
177+
$" {s.GetSessionImplementation().SessionId}"));
178+
await (semaphore.WaitAsync());
179+
180+
for (int j = 0; j < 10; j++)
181+
{
182+
await (s.GetAsync<Person>(i * 10 + j)); //will execute some sql
183+
}
184+
}
185+
}
186+
catch (Exception e)
187+
{
188+
failures.Add(e);
189+
}
190+
})));
191+
192+
Assert.That(failures, Is.Empty, $"{failures.Count} task(s) failed.");
193+
194+
var loggingEvent = spy.GetWholeLog();
195+
for (var i = 1; i < 11; i++)
196+
for (var j = 0; j < 10; j++)
197+
{
198+
var sessionId = sessionIds[i];
199+
Assert.That(loggingEvent, Does.Contain($"p0 = {i * 10 + j} [Type: Int32 (0:0:0)] | SessionId: {sessionId}"));
200+
}
201+
}
202+
}
203+
59204
// IFixingRequired interface ensures the value is evaluated at log time rather than at log buffer flush time.
60205
public class SessionIdCapturer : IFixingRequired
61206
{
@@ -83,7 +228,7 @@ public TextLogSpy(string loggerName, string pattern)
83228
Threshold = Level.All,
84229
Writer = new StringWriter(stringBuilder)
85230
};
86-
loggerImpl = (Logger)LogManager.GetLogger(typeof(LogsFixtureAsync).Assembly, loggerName).Logger;
231+
loggerImpl = (Logger) LogManager.GetLogger(typeof(LogsFixtureAsync).Assembly, loggerName).Logger;
87232
loggerImpl.AddAppender(appender);
88233
previousLevel = loggerImpl.Level;
89234
loggerImpl.Level = Level.All;
@@ -100,7 +245,37 @@ public void Dispose()
100245
loggerImpl.Level = previousLevel;
101246
}
102247
}
103-
}
104248

249+
public class InterlacedSessionInterceptor : EmptyInterceptor
250+
{
251+
private readonly ISessionFactory _sfi;
252+
253+
public System.Collections.Generic.List<Guid> SessionIds { get; } = new System.Collections.Generic.List<Guid>();
105254

255+
public InterlacedSessionInterceptor(ISessionFactory sfi)
256+
{
257+
_sfi = sfi;
258+
}
259+
260+
public override SqlString OnPrepareStatement(SqlString sql)
261+
{
262+
using (var s = _sfi.OpenSession())
263+
{
264+
SessionIds.Add(s.GetSessionImplementation().SessionId);
265+
s.Get<Person>(SessionIds.Count); //will execute some sql
266+
}
267+
return base.OnPrepareStatement(sql);
268+
}
269+
270+
public override bool OnLoad(object entity, object id, object[] state, string[] propertyNames, IType[] types)
271+
{
272+
using (var s = _sfi.OpenSession())
273+
{
274+
SessionIds.Add(s.GetSessionImplementation().SessionId);
275+
s.Get<Person>(SessionIds.Count); //will execute some sql
276+
}
277+
return base.OnLoad(entity, id, state, propertyNames, types);
278+
}
279+
}
280+
}
106281
}

0 commit comments

Comments
 (0)