9
9
10
10
11
11
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;
13
17
using NHibernate.Impl;
14
-
18
+ using NHibernate.SqlCommand;
19
+ using NHibernate.Type;
15
20
using NUnit.Framework;
21
+ using NHibernate.Linq;
16
22
17
23
namespace NHibernate.Test.NHSpecificTest.Logs
18
24
{
@@ -24,7 +30,6 @@ namespace NHibernate.Test.NHSpecificTest.Logs
24
30
using log4net.Core;
25
31
using log4net.Layout;
26
32
using log4net.Repository.Hierarchy;
27
- using System.Threading.Tasks;
28
33
29
34
[TestFixture]
30
35
public class LogsFixtureAsync : TestCase
@@ -39,6 +44,33 @@ protected override string MappingsAssembly
39
44
get { return "NHibernate.Test"; }
40
45
}
41
46
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
+
42
74
[Test]
43
75
public async Task WillGetSessionIdFromSessionLogsAsync()
44
76
{
@@ -47,15 +79,128 @@ public async Task WillGetSessionIdFromSessionLogsAsync()
47
79
using (var spy = new TextLogSpy("NHibernate.SQL", "%message | SessionId: %property{sessionId}"))
48
80
using (var s = Sfi.OpenSession())
49
81
{
50
- var sessionId = ((SessionImpl)s).SessionId;
82
+ var sessionId = ((SessionImpl) s).SessionId;
51
83
52
- await (s.GetAsync<Person>(1));//will execute some sql
84
+ await (s.GetAsync<Person>(1)); //will execute some sql
53
85
54
86
var loggingEvent = spy.GetWholeLog();
55
87
Assert.That(loggingEvent.Contains(sessionId.ToString()), Is.True);
56
88
}
57
89
}
58
90
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
+
59
204
// IFixingRequired interface ensures the value is evaluated at log time rather than at log buffer flush time.
60
205
public class SessionIdCapturer : IFixingRequired
61
206
{
@@ -83,7 +228,7 @@ public TextLogSpy(string loggerName, string pattern)
83
228
Threshold = Level.All,
84
229
Writer = new StringWriter(stringBuilder)
85
230
};
86
- loggerImpl = (Logger)LogManager.GetLogger(typeof(LogsFixtureAsync).Assembly, loggerName).Logger;
231
+ loggerImpl = (Logger) LogManager.GetLogger(typeof(LogsFixtureAsync).Assembly, loggerName).Logger;
87
232
loggerImpl.AddAppender(appender);
88
233
previousLevel = loggerImpl.Level;
89
234
loggerImpl.Level = Level.All;
@@ -100,7 +245,37 @@ public void Dispose()
100
245
loggerImpl.Level = previousLevel;
101
246
}
102
247
}
103
- }
104
248
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>();
105
254
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
+ }
106
281
}
0 commit comments