Skip to content

Commit 6457e7e

Browse files
Fixes cancelation token registration on ReadAsync
1 parent f62ac3b commit 6457e7e

File tree

4 files changed

+62
-12
lines changed

4 files changed

+62
-12
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -4739,6 +4739,12 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
47394739
return Task.FromCanceled<bool>(cancellationToken);
47404740
}
47414741

4742+
IDisposable registration = null;
4743+
if (cancellationToken.CanBeCanceled)
4744+
{
4745+
registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command);
4746+
}
4747+
47424748
// Check for existing async
47434749
if (_currentTask != null)
47444750
{
@@ -4831,12 +4837,6 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
48314837
return source.Task;
48324838
}
48334839

4834-
IDisposable registration = null;
4835-
if (cancellationToken.CanBeCanceled)
4836-
{
4837-
registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command);
4838-
}
4839-
48404840
ReadAsyncCallContext context = null;
48414841
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
48424842
{

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -5332,6 +5332,12 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
53325332
return ADP.CreatedTaskWithCancellation<bool>();
53335333
}
53345334

5335+
IDisposable registration = null;
5336+
if (cancellationToken.CanBeCanceled)
5337+
{
5338+
registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command);
5339+
}
5340+
53355341
// Check for existing async
53365342
if (_currentTask != null)
53375343
{
@@ -5425,12 +5431,6 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
54255431
return source.Task;
54265432
}
54275433

5428-
IDisposable registration = null;
5429-
if (cancellationToken.CanBeCanceled)
5430-
{
5431-
registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command);
5432-
}
5433-
54345434
var context = Interlocked.Exchange(ref _cachedReadAsyncContext, null) ?? new ReadAsyncCallContext();
54355435

54365436
Debug.Assert(context._reader == null && context._source == null && context._disposable == null, "cached ReadAsyncCallContext was not properly disposed");

src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@
272272
<Compile Include="SQL\Common\SystemDataInternals\DataReaderHelper.cs" />
273273
<Compile Include="SQL\Common\SystemDataInternals\TdsParserHelper.cs" />
274274
<Compile Include="SQL\Common\SystemDataInternals\TdsParserStateObjectHelper.cs" />
275+
<Compile Include="SQL\DataReaderTest\DataReaderCancellationTest.cs" />
275276
<Compile Include="SQL\SqlCommand\SqlCommandStoredProcTest.cs" />
276277
<Compile Include="TracingTests\TestTdsServer.cs" />
277278
<Compile Include="XUnitAssemblyAttributes.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Diagnostics;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Xunit;
9+
10+
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
11+
{
12+
public class DataReaderCancellationTest
13+
{
14+
/// <summary>
15+
/// Test ensures cancellation token is registered before ReadAsync starts processing results from TDS Stream,
16+
/// such that when Cancel is triggered, the token is capable of canceling reading further results.
17+
/// </summary>
18+
/// <returns>Async Task</returns>
19+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
20+
public static async Task CancellationTokenIsRespected_ReadAsync()
21+
{
22+
const string longRunningQuery = @"
23+
with TenRows as (select Value from (values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) as TenRows (Value)),
24+
ThousandRows as (select A.Value as A, B.Value as B, C.Value as C from TenRows as A, TenRows as B, TenRows as C)
25+
select *
26+
from ThousandRows as A, ThousandRows as B, ThousandRows as C;";
27+
28+
using (var source = new CancellationTokenSource())
29+
using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
30+
{
31+
await connection.OpenAsync(source.Token);
32+
33+
Stopwatch stopwatch = Stopwatch.StartNew();
34+
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
35+
{
36+
using (var command = new SqlCommand(longRunningQuery, connection))
37+
using (var reader = await command.ExecuteReaderAsync(source.Token))
38+
{
39+
while (await reader.ReadAsync(source.Token))
40+
{
41+
source.Cancel();
42+
}
43+
}
44+
});
45+
Assert.True(stopwatch.ElapsedMilliseconds < 1000, "Cancellation did not trigger on time.");
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)