Skip to content

Commit 556a4a7

Browse files
SoftStoneDevelopVyacheslavfredericDelaportehazzik
authored
Added support for the CancelQuery() method in IStatelessSession (#3074)
Fix #809 Co-authored-by: Vyacheslav <[email protected]> Co-authored-by: Frédéric Delaporte <[email protected]> Co-authored-by: Alex Zaytsev <[email protected]>
1 parent d894c41 commit 556a4a7

8 files changed

+275
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Data;
13+
using System.Data.SqlClient;
14+
using System.Globalization;
15+
using System.Linq;
16+
using System.Threading;
17+
using System.Threading.Tasks;
18+
using NHibernate.Linq;
19+
using NUnit.Framework;
20+
21+
namespace NHibernate.Test.Stateless
22+
{
23+
[TestFixture]
24+
public class StatelessSessionCancelQueryFixtureAsync : TestCase
25+
{
26+
protected override string MappingsAssembly
27+
{
28+
get { return "NHibernate.Test"; }
29+
}
30+
31+
protected override string[] Mappings
32+
{
33+
get { return new[] { "Stateless.Document.hbm.xml" }; }
34+
}
35+
36+
private const string _documentName = "SomeDocument";
37+
private CultureInfo _backupCulture;
38+
private CultureInfo _backupUICulture;
39+
40+
protected override void OnSetUp()
41+
{
42+
if (CultureInfo.CurrentCulture.TwoLetterISOLanguageName != CultureInfo.InvariantCulture.TwoLetterISOLanguageName)
43+
{
44+
// This test needs to run in English
45+
_backupCulture = CultureInfo.CurrentCulture;
46+
_backupUICulture = CultureInfo.CurrentUICulture;
47+
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
48+
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
49+
}
50+
51+
using (var s = Sfi.OpenStatelessSession())
52+
using (var t = s.BeginTransaction())
53+
{
54+
s.Insert(new Document("Some text", _documentName));
55+
t.Commit();
56+
}
57+
}
58+
59+
protected override void OnTearDown()
60+
{
61+
using (var s = Sfi.OpenStatelessSession())
62+
using (var t = s.BeginTransaction())
63+
{
64+
s.CreateQuery("delete Document").ExecuteUpdate();
65+
t.Commit();
66+
}
67+
68+
if (_backupCulture != null)
69+
{
70+
CultureInfo.CurrentCulture = _backupCulture;
71+
CultureInfo.CurrentUICulture = _backupUICulture;
72+
}
73+
}
74+
75+
protected override bool AppliesTo(Dialect.Dialect dialect)
76+
{
77+
return TestDialect.SupportsCancelQuery &&
78+
TestDialect.SupportsSelectForUpdate;
79+
}
80+
81+
private async Task CancelQueryTestAsync(Action<IStatelessSession> queryAction, CancellationToken cancellationToken = default(CancellationToken))
82+
{
83+
using (var s1 = Sfi.OpenStatelessSession())
84+
using (var t1 = s1.BeginTransaction())
85+
{
86+
await (s1.GetAsync<Document>(_documentName, LockMode.Upgrade, cancellationToken));
87+
88+
using (var s2 = Sfi.OpenStatelessSession())
89+
using (var t2 = s2.BeginTransaction())
90+
{
91+
var queryTask = Task.Factory.StartNew(() => queryAction(s2));
92+
93+
await (Task.Delay(200, cancellationToken));
94+
s2.CancelQuery();
95+
Assert.That(() => queryTask,
96+
Throws.InnerException.TypeOf(typeof(OperationCanceledException))
97+
.Or.InnerException.Message.Contains("cancel"));
98+
}
99+
}
100+
}
101+
102+
[Test]
103+
public async Task CancelHqlQueryAsync()
104+
{
105+
await (CancelQueryTestAsync(s => s.CreateQuery("from Document d").SetLockMode("d", LockMode.Upgrade).List<Document>()));
106+
}
107+
108+
[Test]
109+
public async Task CancelLinqQueryAsync()
110+
{
111+
await (CancelQueryTestAsync(s => s.Query<Document>().WithLock(LockMode.Upgrade).ToList()));
112+
}
113+
114+
[Test]
115+
public async Task CancelQueryOverQueryAsync()
116+
{
117+
await (CancelQueryTestAsync(s => s.QueryOver<Document>().Lock().Upgrade.List()));
118+
}
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Data;
3+
using System.Data.SqlClient;
4+
using System.Globalization;
5+
using System.Linq;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using NHibernate.Linq;
9+
using NUnit.Framework;
10+
11+
namespace NHibernate.Test.Stateless
12+
{
13+
[TestFixture]
14+
public class StatelessSessionCancelQueryFixture : TestCase
15+
{
16+
protected override string MappingsAssembly
17+
{
18+
get { return "NHibernate.Test"; }
19+
}
20+
21+
protected override string[] Mappings
22+
{
23+
get { return new[] { "Stateless.Document.hbm.xml" }; }
24+
}
25+
26+
private const string _documentName = "SomeDocument";
27+
private CultureInfo _backupCulture;
28+
private CultureInfo _backupUICulture;
29+
30+
protected override void OnSetUp()
31+
{
32+
if (CultureInfo.CurrentCulture.TwoLetterISOLanguageName != CultureInfo.InvariantCulture.TwoLetterISOLanguageName)
33+
{
34+
// This test needs to run in English
35+
_backupCulture = CultureInfo.CurrentCulture;
36+
_backupUICulture = CultureInfo.CurrentUICulture;
37+
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
38+
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
39+
}
40+
41+
using (var s = Sfi.OpenStatelessSession())
42+
using (var t = s.BeginTransaction())
43+
{
44+
s.Insert(new Document("Some text", _documentName));
45+
t.Commit();
46+
}
47+
}
48+
49+
protected override void OnTearDown()
50+
{
51+
using (var s = Sfi.OpenStatelessSession())
52+
using (var t = s.BeginTransaction())
53+
{
54+
s.CreateQuery("delete Document").ExecuteUpdate();
55+
t.Commit();
56+
}
57+
58+
if (_backupCulture != null)
59+
{
60+
CultureInfo.CurrentCulture = _backupCulture;
61+
CultureInfo.CurrentUICulture = _backupUICulture;
62+
}
63+
}
64+
65+
protected override bool AppliesTo(Dialect.Dialect dialect)
66+
{
67+
return TestDialect.SupportsCancelQuery &&
68+
TestDialect.SupportsSelectForUpdate;
69+
}
70+
71+
private void CancelQueryTest(Action<IStatelessSession> queryAction)
72+
{
73+
using (var s1 = Sfi.OpenStatelessSession())
74+
using (var t1 = s1.BeginTransaction())
75+
{
76+
s1.Get<Document>(_documentName, LockMode.Upgrade);
77+
78+
using (var s2 = Sfi.OpenStatelessSession())
79+
using (var t2 = s2.BeginTransaction())
80+
{
81+
var queryTask = Task.Factory.StartNew(() => queryAction(s2));
82+
83+
Thread.Sleep(200);
84+
s2.CancelQuery();
85+
Assert.That(() => queryTask,
86+
Throws.InnerException.TypeOf(typeof(OperationCanceledException))
87+
.Or.InnerException.Message.Contains("cancel"));
88+
}
89+
}
90+
}
91+
92+
[Test]
93+
public void CancelHqlQuery()
94+
{
95+
CancelQueryTest(s => s.CreateQuery("from Document d").SetLockMode("d", LockMode.Upgrade).List<Document>());
96+
}
97+
98+
[Test]
99+
public void CancelLinqQuery()
100+
{
101+
CancelQueryTest(s => s.Query<Document>().WithLock(LockMode.Upgrade).ToList());
102+
}
103+
104+
[Test]
105+
public void CancelQueryOverQuery()
106+
{
107+
CancelQueryTest(s => s.QueryOver<Document>().Lock().Upgrade.List());
108+
}
109+
}
110+
}

src/NHibernate.Test/TestDialect.cs

+5
Original file line numberDiff line numberDiff line change
@@ -198,5 +198,10 @@ public bool SupportsSqlType(SqlType sqlType)
198198
/// Returns true if you can modify the same table which you use in the SELECT part.
199199
/// </summary>
200200
public virtual bool SupportsModifyAndSelectSameTable => true;
201+
202+
/// <summary>
203+
/// Returns true if you can cancel a query.
204+
/// </summary>
205+
public virtual bool SupportsCancelQuery => true;
201206
}
202207
}

src/NHibernate.Test/TestDialects/MsSql2008TestDialect.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace NHibernate.Test.TestDialects
1+
using System.Runtime.InteropServices;
2+
3+
namespace NHibernate.Test.TestDialects
24
{
35
public class MsSql2008TestDialect : TestDialect
46
{
@@ -11,5 +13,9 @@ public MsSql2008TestDialect(Dialect.Dialect dialect)
1113
/// Does not support SELECT FOR UPDATE with paging
1214
/// </summary>
1315
public override bool SupportsSelectForUpdateWithPaging => false;
16+
17+
/// <inheritdoc />
18+
/// <remarks>Canceling a query hangs under Linux with Sql2008ClientDriver. (It may be a data provider bug fixed with MicrosoftDataSqlClientDriver.)</remarks>
19+
public override bool SupportsCancelQuery => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
1420
}
1521
}

src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace NHibernate.Test.TestDialects
1+
using System.Runtime.InteropServices;
2+
3+
namespace NHibernate.Test.TestDialects
24
{
35
public class Oracle10gTestDialect : TestDialect
46
{
@@ -12,5 +14,9 @@ public Oracle10gTestDialect(Dialect.Dialect dialect) : base(dialect)
1214
public override bool SupportsSelectForUpdateWithPaging => false;
1315

1416
public override bool SupportsAggregateInSubSelect => true;
17+
18+
/// <inheritdoc />
19+
/// <remarks>Canceling a query hangs under Linux with OracleManagedDataClientDriver 21.6.1.</remarks>
20+
public override bool SupportsCancelQuery => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
1521
}
1622
}

src/NHibernate/AdoNet/AbstractBatcher.cs

+5
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ private DbDataReader DoExecuteReader(DbCommand cmd)
253253
try
254254
{
255255
var reader = cmd.ExecuteReader();
256+
if (reader == null)
257+
{
258+
// MySql may return null instead of an exception, by example when the query is canceled by another thread.
259+
throw new InvalidOperationException("The query execution has yielded a null reader. (Has it been canceled?)");
260+
}
256261
return _factory.ConnectionProvider.Driver.SupportsMultipleOpenReaders
257262
? reader
258263
: NHybridDataReader.Create(reader);

src/NHibernate/Async/AdoNet/AbstractBatcher.cs

+5
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ private async Task<DbDataReader> DoExecuteReaderAsync(DbCommand cmd, Cancellatio
168168
try
169169
{
170170
var reader = await (cmd.ExecuteReaderAsync(cancellationToken)).ConfigureAwait(false);
171+
if (reader == null)
172+
{
173+
// MySql may return null instead of an exception, by example when the query is canceled by another thread.
174+
throw new InvalidOperationException("The query execution has yielded a null reader. (Has it been canceled?)");
175+
}
171176
return _factory.ConnectionProvider.Driver.SupportsMultipleOpenReaders
172177
? reader
173178
: await (NHybridDataReader.CreateAsync(reader, cancellationToken)).ConfigureAwait(false);

src/NHibernate/IStatelessSession.cs

+16
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ public static void FlushBatcher(this IStatelessSession session)
7474
{
7575
session.GetSessionImplementation().Flush();
7676
}
77+
78+
/// <summary>
79+
/// Cancel execution of the current query.
80+
/// </summary>
81+
/// <remarks>
82+
/// May be called from one thread to stop execution of a query in another thread.
83+
/// Use with care!
84+
/// </remarks>
85+
public static void CancelQuery(this IStatelessSession session)
86+
{
87+
var implementation = session.GetSessionImplementation();
88+
using (implementation.BeginProcess())
89+
{
90+
implementation.Batcher.CancelLastQuery();
91+
}
92+
}
7793
}
7894

7995
/// <summary>

0 commit comments

Comments
 (0)