Skip to content

Commit 291d26a

Browse files
Fix thread safety of bitwise functions
Functions instances are hold in the dialect, shared among sessions spawned from the same factory. They must be thread safe. Fix #1355 in 5.0.x (cherry picked from commit 231b080)
1 parent 1188ca6 commit 291d26a

File tree

4 files changed

+479
-113
lines changed

4 files changed

+479
-113
lines changed

src/NHibernate.Test/Async/Hql/HQLFunctions.cs

Lines changed: 211 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@
1010

1111
using System;
1212
using System.Collections;
13+
using System.Collections.Concurrent;
14+
using System.Collections.Generic;
15+
using System.Threading;
16+
using System.Threading.Tasks;
1317
using NHibernate.Dialect;
1418
using NUnit.Framework;
1519

1620
namespace NHibernate.Test.Hql
1721
{
18-
using System.Threading.Tasks;
22+
using System.Linq;
1923
/// <summary>
2024
/// This test run each HQL function separately so is easy to know which function need
2125
/// an override in the specific dialect implementation.
@@ -32,6 +36,19 @@ static HQLFunctionsAsync()
3236
{"locate", new[] {typeof (SQLiteDialect)}},
3337
{"bit_length", new[] {typeof (SQLiteDialect)}},
3438
{"extract", new[] {typeof (SQLiteDialect)}},
39+
{
40+
"bxor",
41+
new[]
42+
{
43+
// Could be supported like Oracle, with a template
44+
typeof (SQLiteDialect),
45+
// Could be supported by overriding registration with # instead of ^
46+
typeof (PostgreSQLDialect),
47+
typeof (PostgreSQL81Dialect),
48+
typeof (PostgreSQL82Dialect),
49+
typeof (PostgreSQL83Dialect)
50+
}
51+
},
3552
{
3653
"nullif",
3754
new[]
@@ -1079,5 +1096,198 @@ public async Task ParameterLikeArgumentAsync()
10791096
Assert.AreEqual(1, l.Count);
10801097
}
10811098
}
1099+
1100+
[Test]
1101+
public async Task BitwiseAndAsync()
1102+
{
1103+
IgnoreIfNotSupported("band");
1104+
await (CreateMaterialResourcesAsync());
1105+
1106+
using (var s = OpenSession())
1107+
using (var tx = s.BeginTransaction())
1108+
{
1109+
var query = s.CreateQuery("from MaterialResource m where (m.State & 1) > 0");
1110+
var result = await (query.ListAsync());
1111+
Assert.That(result, Has.Count.EqualTo(1), "& 1");
1112+
1113+
query = s.CreateQuery("from MaterialResource m where (m.State & 2) > 0");
1114+
result = await (query.ListAsync());
1115+
Assert.That(result, Has.Count.EqualTo(1), "& 2");
1116+
1117+
query = s.CreateQuery("from MaterialResource m where (m.State & 3) > 0");
1118+
result = await (query.ListAsync());
1119+
Assert.That(result, Has.Count.EqualTo(2), "& 3");
1120+
1121+
await (tx.CommitAsync());
1122+
}
1123+
await (DeleteMaterialResourcesAsync());
1124+
}
1125+
1126+
[Test]
1127+
public async Task BitwiseOrAsync()
1128+
{
1129+
IgnoreIfNotSupported("bor");
1130+
await (CreateMaterialResourcesAsync());
1131+
1132+
using (var s = OpenSession())
1133+
using (var tx = s.BeginTransaction())
1134+
{
1135+
var query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 0");
1136+
var result = await (query.ListAsync());
1137+
Assert.That(result, Has.Count.EqualTo(3), "| 1) > 0");
1138+
1139+
query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 1");
1140+
result = await (query.ListAsync());
1141+
Assert.That(result, Has.Count.EqualTo(1), "| 1) > 1");
1142+
1143+
query = s.CreateQuery("from MaterialResource m where (m.State | 0) > 0");
1144+
result = await (query.ListAsync());
1145+
Assert.That(result, Has.Count.EqualTo(2), "| 0) > 0");
1146+
1147+
await (tx.CommitAsync());
1148+
}
1149+
await (DeleteMaterialResourcesAsync());
1150+
}
1151+
1152+
[Test]
1153+
public async Task BitwiseXorAsync()
1154+
{
1155+
IgnoreIfNotSupported("bxor");
1156+
await (CreateMaterialResourcesAsync());
1157+
1158+
using (var s = OpenSession())
1159+
using (var tx = s.BeginTransaction())
1160+
{
1161+
var query = s.CreateQuery("from MaterialResource m where (m.State ^ 1) > 0");
1162+
var result = await (query.ListAsync());
1163+
Assert.That(result, Has.Count.EqualTo(2), "^ 1");
1164+
1165+
query = s.CreateQuery("from MaterialResource m where (m.State ^ 2) > 0");
1166+
result = await (query.ListAsync());
1167+
Assert.That(result, Has.Count.EqualTo(2), "^ 2");
1168+
1169+
query = s.CreateQuery("from MaterialResource m where (m.State ^ 3) > 0");
1170+
result = await (query.ListAsync());
1171+
Assert.That(result, Has.Count.EqualTo(3), "^ 3");
1172+
1173+
await (tx.CommitAsync());
1174+
}
1175+
await (DeleteMaterialResourcesAsync());
1176+
}
1177+
1178+
[Test]
1179+
public async Task BitwiseNotAsync()
1180+
{
1181+
IgnoreIfNotSupported("bnot");
1182+
IgnoreIfNotSupported("band");
1183+
await (CreateMaterialResourcesAsync());
1184+
1185+
using (var s = OpenSession())
1186+
using (var tx = s.BeginTransaction())
1187+
{
1188+
// ! takes not precedence over & at least with some dialects (maybe all).
1189+
var query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 3");
1190+
var result = await (query.ListAsync());
1191+
Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 3");
1192+
1193+
query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 2");
1194+
result = await (query.ListAsync());
1195+
Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 2");
1196+
1197+
query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 1");
1198+
result = await (query.ListAsync());
1199+
Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 1");
1200+
1201+
await (tx.CommitAsync());
1202+
}
1203+
await (DeleteMaterialResourcesAsync());
1204+
}
1205+
1206+
// #1670
1207+
[Test]
1208+
public async Task BitwiseIsThreadsafeAsync()
1209+
{
1210+
IgnoreIfNotSupported("band");
1211+
IgnoreIfNotSupported("bor");
1212+
IgnoreIfNotSupported("bxor");
1213+
IgnoreIfNotSupported("bnot");
1214+
var queries = new List<Tuple<string, int>>
1215+
{
1216+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State & 1) > 0", 1),
1217+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State & 2) > 0", 1),
1218+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State & 3) > 0", 2),
1219+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State | 1) > 0", 3),
1220+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State | 1) > 1", 1),
1221+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State | 0) > 0", 2),
1222+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State ^ 1) > 0", 2),
1223+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State ^ 2) > 0", 2),
1224+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State ^ 3) > 0", 3),
1225+
new Tuple<string, int> ("select count(*) from MaterialResource m where ((!m.State) & 3) = 3", 1),
1226+
new Tuple<string, int> ("select count(*) from MaterialResource m where ((!m.State) & 3) = 2", 1),
1227+
new Tuple<string, int> ("select count(*) from MaterialResource m where ((!m.State) & 3) = 1", 1)
1228+
};
1229+
// Do not use a ManualResetEventSlim, it does not support async and exhausts the task thread pool in the
1230+
// async counterparts of this test. SemaphoreSlim has the async support and release the thread when waiting.
1231+
var semaphore = new SemaphoreSlim(0);
1232+
var failures = new ConcurrentBag<Exception>();
1233+
1234+
await (CreateMaterialResourcesAsync());
1235+
1236+
await (Task.WhenAll(
1237+
Enumerable.Range(0, queries.Count + 1 - 0).Select(async i =>
1238+
{
1239+
if (i >= queries.Count)
1240+
{
1241+
// Give some time to threads for reaching the wait, having all of them ready to do the
1242+
// critical part of their job concurrently.
1243+
await (Task.Delay(100));
1244+
semaphore.Release(queries.Count);
1245+
return;
1246+
}
1247+
1248+
try
1249+
{
1250+
var query = queries[i];
1251+
using (var s = OpenSession())
1252+
using (var tx = s.BeginTransaction())
1253+
{
1254+
await (semaphore.WaitAsync());
1255+
var q = s.CreateQuery(query.Item1);
1256+
var result = await (q.UniqueResultAsync<long>());
1257+
Assert.That(result, Is.EqualTo(query.Item2), query.Item1);
1258+
await (tx.CommitAsync());
1259+
}
1260+
}
1261+
catch (Exception e)
1262+
{
1263+
failures.Add(e);
1264+
}
1265+
})));
1266+
1267+
Assert.That(failures, Is.Empty, $"{failures.Count} task(s) failed.");
1268+
await (DeleteMaterialResourcesAsync());
1269+
}
1270+
1271+
private async Task CreateMaterialResourcesAsync(CancellationToken cancellationToken = default(CancellationToken))
1272+
{
1273+
using (var s = OpenSession())
1274+
using (var tx = s.BeginTransaction())
1275+
{
1276+
await (s.SaveAsync(new MaterialResource("m1", "18", MaterialResource.MaterialState.Available), cancellationToken));
1277+
await (s.SaveAsync(new MaterialResource("m2", "19", MaterialResource.MaterialState.Reserved), cancellationToken));
1278+
await (s.SaveAsync(new MaterialResource("m3", "20", MaterialResource.MaterialState.Discarded), cancellationToken));
1279+
await (tx.CommitAsync(cancellationToken));
1280+
}
1281+
}
1282+
1283+
private async Task DeleteMaterialResourcesAsync(CancellationToken cancellationToken = default(CancellationToken))
1284+
{
1285+
using (var s = OpenSession())
1286+
using (var tx = s.BeginTransaction())
1287+
{
1288+
await (s.CreateQuery("delete from MaterialResource").ExecuteUpdateAsync(cancellationToken));
1289+
await (tx.CommitAsync(cancellationToken));
1290+
}
1291+
}
10821292
}
10831293
}

0 commit comments

Comments
 (0)