Skip to content

Commit 3ac9f87

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 4.1.x (cherry picked from commit 66630f9)
1 parent b64b6b7 commit 3ac9f87

File tree

3 files changed

+267
-112
lines changed

3 files changed

+267
-112
lines changed

src/NHibernate.Test/Hql/HQLFunctions.cs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using System;
22
using System.Collections;
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using System.Threading.Tasks;
37
using NHibernate.Dialect;
48
using NUnit.Framework;
59

@@ -21,6 +25,18 @@ static HQLFunctions()
2125
{"locate", new[] {typeof (SQLiteDialect)}},
2226
{"bit_length", new[] {typeof (SQLiteDialect)}},
2327
{"extract", new[] {typeof (SQLiteDialect)}},
28+
{
29+
"bxor",
30+
new[]
31+
{
32+
// Could be supported like Oracle, with a template
33+
typeof (SQLiteDialect),
34+
// Could be supported by overriding registration with # instead of ^
35+
typeof (PostgreSQLDialect),
36+
typeof (PostgreSQL81Dialect),
37+
typeof (PostgreSQL82Dialect)
38+
}
39+
},
2440
{"nullif", new[] {typeof (Oracle8iDialect)}}
2541
};
2642
}
@@ -1014,7 +1030,202 @@ public void ParameterLikeArgument()
10141030
Assert.AreEqual(1, l.Count);
10151031
}
10161032
}
1033+
1034+
[Test]
1035+
public void BitwiseAnd()
1036+
{
1037+
IgnoreIfNotSupported("band");
1038+
CreateMaterialResources();
1039+
1040+
using (var s = OpenSession())
1041+
using (var tx = s.BeginTransaction())
1042+
{
1043+
var query = s.CreateQuery("from MaterialResource m where (m.State & 1) > 0");
1044+
var result = query.List();
1045+
Assert.That(result, Has.Count.EqualTo(1), "& 1");
1046+
1047+
query = s.CreateQuery("from MaterialResource m where (m.State & 2) > 0");
1048+
result = query.List();
1049+
Assert.That(result, Has.Count.EqualTo(1), "& 2");
1050+
1051+
query = s.CreateQuery("from MaterialResource m where (m.State & 3) > 0");
1052+
result = query.List();
1053+
Assert.That(result, Has.Count.EqualTo(2), "& 3");
1054+
1055+
tx.Commit();
1056+
}
1057+
DeleteMaterialResources();
1058+
}
1059+
1060+
[Test]
1061+
public void BitwiseOr()
1062+
{
1063+
IgnoreIfNotSupported("bor");
1064+
CreateMaterialResources();
1065+
1066+
using (var s = OpenSession())
1067+
using (var tx = s.BeginTransaction())
1068+
{
1069+
var query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 0");
1070+
var result = query.List();
1071+
Assert.That(result, Has.Count.EqualTo(3), "| 1) > 0");
1072+
1073+
query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 1");
1074+
result = query.List();
1075+
Assert.That(result, Has.Count.EqualTo(1), "| 1) > 1");
1076+
1077+
query = s.CreateQuery("from MaterialResource m where (m.State | 0) > 0");
1078+
result = query.List();
1079+
Assert.That(result, Has.Count.EqualTo(2), "| 0) > 0");
1080+
1081+
tx.Commit();
1082+
}
1083+
DeleteMaterialResources();
1084+
}
1085+
1086+
[Test]
1087+
public void BitwiseXor()
1088+
{
1089+
IgnoreIfNotSupported("bxor");
1090+
CreateMaterialResources();
1091+
1092+
using (var s = OpenSession())
1093+
using (var tx = s.BeginTransaction())
1094+
{
1095+
var query = s.CreateQuery("from MaterialResource m where (m.State ^ 1) > 0");
1096+
var result = query.List();
1097+
Assert.That(result, Has.Count.EqualTo(2), "^ 1");
1098+
1099+
query = s.CreateQuery("from MaterialResource m where (m.State ^ 2) > 0");
1100+
result = query.List();
1101+
Assert.That(result, Has.Count.EqualTo(2), "^ 2");
1102+
1103+
query = s.CreateQuery("from MaterialResource m where (m.State ^ 3) > 0");
1104+
result = query.List();
1105+
Assert.That(result, Has.Count.EqualTo(3), "^ 3");
1106+
1107+
tx.Commit();
1108+
}
1109+
DeleteMaterialResources();
1110+
}
1111+
1112+
[Test]
1113+
public void BitwiseNot()
1114+
{
1115+
IgnoreIfNotSupported("bnot");
1116+
IgnoreIfNotSupported("band");
1117+
CreateMaterialResources();
1118+
1119+
using (var s = OpenSession())
1120+
using (var tx = s.BeginTransaction())
1121+
{
1122+
// ! takes not precedence over & at least with some dialects (maybe all).
1123+
var query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 3");
1124+
var result = query.List();
1125+
Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 3");
1126+
1127+
query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 2");
1128+
result = query.List();
1129+
Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 2");
1130+
1131+
query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 1");
1132+
result = query.List();
1133+
Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 1");
1134+
1135+
tx.Commit();
1136+
}
1137+
DeleteMaterialResources();
1138+
}
1139+
1140+
// #1670
1141+
[Test]
1142+
public void BitwiseIsThreadsafe()
1143+
{
1144+
IgnoreIfNotSupported("band");
1145+
IgnoreIfNotSupported("bor");
1146+
IgnoreIfNotSupported("bxor");
1147+
IgnoreIfNotSupported("bnot");
1148+
var queries = new List<Tuple<string, int>>
1149+
{
1150+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State & 1) > 0", 1),
1151+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State & 2) > 0", 1),
1152+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State & 3) > 0", 2),
1153+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State | 1) > 0", 3),
1154+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State | 1) > 1", 1),
1155+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State | 0) > 0", 2),
1156+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State ^ 1) > 0", 2),
1157+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State ^ 2) > 0", 2),
1158+
new Tuple<string, int> ("select count(*) from MaterialResource m where (m.State ^ 3) > 0", 3),
1159+
new Tuple<string, int> ("select count(*) from MaterialResource m where ((!m.State) & 3) = 3", 1),
1160+
new Tuple<string, int> ("select count(*) from MaterialResource m where ((!m.State) & 3) = 2", 1),
1161+
new Tuple<string, int> ("select count(*) from MaterialResource m where ((!m.State) & 3) = 1", 1)
1162+
};
1163+
// Do not use a ManualResetEventSlim, it does not support async and exhausts the task thread pool in the
1164+
// async counterparts of this test. SemaphoreSlim has the async support and release the thread when waiting.
1165+
var semaphore = new SemaphoreSlim(0);
1166+
var failures = new ConcurrentBag<Exception>();
1167+
1168+
CreateMaterialResources();
1169+
1170+
Parallel.For(
1171+
0, queries.Count + 1,
1172+
i =>
1173+
{
1174+
if (i >= queries.Count)
1175+
{
1176+
// Give some time to threads for reaching the wait, having all of them ready to do the
1177+
// critical part of their job concurrently.
1178+
Thread.Sleep(100);
1179+
semaphore.Release(queries.Count);
1180+
return;
1181+
}
1182+
1183+
try
1184+
{
1185+
var query = queries[i];
1186+
using (var s = OpenSession())
1187+
using (var tx = s.BeginTransaction())
1188+
{
1189+
semaphore.Wait();
1190+
var q = s.CreateQuery(query.Item1);
1191+
var result = q.UniqueResult<long>();
1192+
Assert.That(result, Is.EqualTo(query.Item2), query.Item1);
1193+
tx.Commit();
1194+
}
1195+
}
1196+
catch (Exception e)
1197+
{
1198+
failures.Add(e);
1199+
}
1200+
});
1201+
1202+
Assert.That(failures, Is.Empty, $"{failures.Count} task(s) failed.");
1203+
DeleteMaterialResources();
1204+
}
1205+
1206+
private void CreateMaterialResources()
1207+
{
1208+
using (var s = OpenSession())
1209+
using (var tx = s.BeginTransaction())
1210+
{
1211+
s.Save(new MaterialResource("m1", "18", MaterialResource.MaterialState.Available));
1212+
s.Save(new MaterialResource("m2", "19", MaterialResource.MaterialState.Reserved));
1213+
s.Save(new MaterialResource("m3", "20", MaterialResource.MaterialState.Discarded));
1214+
tx.Commit();
1215+
}
1216+
}
1217+
1218+
private void DeleteMaterialResources()
1219+
{
1220+
using (var s = OpenSession())
1221+
using (var tx = s.BeginTransaction())
1222+
{
1223+
s.CreateQuery("delete from MaterialResource").ExecuteUpdate();
1224+
tx.Commit();
1225+
}
1226+
}
10171227
}
1228+
10181229
public class ForNh1725
10191230
{
10201231
public string Description { get; set; }

src/NHibernate/Dialect/BitwiseFunctionOperation.cs

Lines changed: 27 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,99 +14,63 @@ namespace NHibernate.Dialect
1414
public class BitwiseFunctionOperation : ISQLFunction
1515
{
1616
private readonly string _functionName;
17-
private SqlStringBuilder _sqlBuffer;
18-
private Queue _args;
1917

2018
/// <summary>
21-
/// Creates an instance of this class using the provided function name
19+
/// Creates an instance of this class using the provided function name.
2220
/// </summary>
2321
/// <param name="functionName">
24-
/// The bitwise function name as defined by the SQL-Dialect
22+
/// The bitwise function name as defined by the SQL-Dialect.
2523
/// </param>
2624
public BitwiseFunctionOperation(string functionName)
2725
{
28-
_functionName = functionName;
26+
_functionName = functionName;
2927
}
3028

3129
#region ISQLFunction Members
3230

31+
/// <inheritdoc />
3332
public IType ReturnType(IType columnType, IMapping mapping)
3433
{
3534
return NHibernateUtil.Int64;
3635
}
3736

38-
public bool HasArguments
39-
{
40-
get { return true; }
41-
}
37+
/// <inheritdoc />
38+
public bool HasArguments => true;
4239

43-
public bool HasParenthesesIfNoArguments
44-
{
45-
get { return true; }
46-
}
40+
/// <inheritdoc />
41+
public bool HasParenthesesIfNoArguments => true;
4742

43+
/// <inheritdoc />
4844
public SqlString Render(IList args, ISessionFactoryImplementor factory)
4945
{
50-
Prepare(args);
51-
52-
AddFunctionName();
53-
OpenParens();
54-
AddArguments();
55-
CloseParens();
56-
57-
return SqlResult();
58-
}
59-
60-
#endregion
46+
var sqlBuffer = new SqlStringBuilder();
6147

62-
private void Prepare(IList args)
63-
{
64-
_sqlBuffer = new SqlStringBuilder();
65-
_args = new Queue();
48+
sqlBuffer.Add(_functionName);
49+
sqlBuffer.Add("(");
6650
foreach (var arg in args)
6751
{
68-
if (!IsParens(arg.ToString()))
69-
_args.Enqueue(arg);
70-
}
71-
}
72-
73-
private static bool IsParens(string candidate)
74-
{
75-
return candidate == "(" || candidate == ")";
76-
}
77-
78-
private void AddFunctionName()
79-
{
80-
_sqlBuffer.Add(_functionName);
81-
}
82-
83-
private void OpenParens()
84-
{
85-
_sqlBuffer.Add("(");
86-
}
87-
88-
private void AddArguments()
89-
{
90-
while (_args.Count > 0)
91-
{
92-
var arg = _args.Dequeue();
52+
// The actual second argument may be surrounded by parentesis as additional arguments.
53+
// They have to be ignored, otherwise it would emit "functionName(firstArg, (, secondArg, ))"
54+
if (IsParens(arg.ToString()))
55+
continue;
9356
if (arg is Parameter || arg is SqlString)
94-
_sqlBuffer.AddObject(arg);
57+
sqlBuffer.AddObject(arg);
9558
else
96-
_sqlBuffer.Add(arg.ToString());
97-
if (_args.Count > 0)
98-
_sqlBuffer.Add(", ");
59+
sqlBuffer.Add(arg.ToString());
60+
sqlBuffer.Add(", ");
9961
}
100-
}
10162

102-
private void CloseParens()
103-
{
104-
_sqlBuffer.Add(")");
63+
sqlBuffer.RemoveAt(sqlBuffer.Count - 1);
64+
sqlBuffer.Add(")");
65+
66+
return sqlBuffer.ToSqlString();
10567
}
10668

107-
private SqlString SqlResult()
69+
#endregion
70+
71+
private static bool IsParens(string candidate)
10872
{
109-
return _sqlBuffer.ToSqlString();
73+
return candidate == "(" || candidate == ")";
11074
}
11175
}
11276
}

0 commit comments

Comments
 (0)