SqlBulkCopy.WriteToServerAsync cancellation does not interrupt a table-lock wait
メンテナーはふだん 2 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 25/100
調査の方向性
The bulk-completion path is in src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlBulkCopy.cs, around CopyBatchesAsyncContinuedOnSuccess, where the report says a synchronous RunParser() call runs without the operation's cancellation token. Start by running the reproduction against a disposable SQL Server with a table lock held, then trace where that synchronous read blocks. Done means canceling the token returns promptly with an OperationCanceledException instead of waiting for BulkCopyTimeout.
索引モデルが issue の本文から書いたものです。
説明
Describe the bug
SqlBulkCopy.WriteToServerAsync can remain blocked by a SQL Server table lock after its cancellation token is canceled. It returns only when BulkCopyTimeout expires, with a timeout SqlException, rather than promptly abandoning the operation. Applications cannot use the token to bound this server-side wait.
To reproduce
Run this console program with Microsoft.Data.SqlClient 7.1.1 on .NET 10. Set SQLCLIENT_PROBE_CONNECTION to a disposable SQL Server database connection string. The account needs DDL permission and access to sys.dm_exec_requests (the reproduction uses the SQL Server container's sa account).
The program observes that the bulk-copy session is blocked before canceling. Task.Run isolates the invocation because the async API can reach the synchronous server-read path before returning its Task.
using System.Data;
using System.Diagnostics;
using Microsoft.Data.SqlClient;
var cs = Environment.GetEnvironmentVariable("SQLCLIENT_PROBE_CONNECTION")!;
await using var observer = new SqlConnection(cs);
await observer.OpenAsync();
var name = "[dbo].[cancel_" + Guid.NewGuid().ToString("N") + "]";
using (var create = new SqlCommand($"CREATE TABLE {name} (id int NOT NULL)", observer))
await create.ExecuteNonQueryAsync();
try
{
await using var writer = new SqlConnection(cs);
await writer.OpenAsync();
await using var writeTransaction = (SqlTransaction)await writer.BeginTransactionAsync();
int session;
using (var spid = new SqlCommand("SELECT @@SPID", writer, writeTransaction))
session = Convert.ToInt32(await spid.ExecuteScalarAsync());
await using var blocker = new SqlConnection(cs);
await blocker.OpenAsync();
await using var blockingTransaction = (SqlTransaction)await blocker.BeginTransactionAsync();
using (var block = new SqlCommand($"SELECT COUNT_BIG(*) FROM {name} WITH (TABLOCKX, HOLDLOCK)", blocker, blockingTransaction))
await block.ExecuteScalarAsync();
using var input = new DataTable();
input.Columns.Add("id", typeof(int));
input.Rows.Add(1);
using var copy = new SqlBulkCopy(writer, SqlBulkCopyOptions.Default, writeTransaction)
{ DestinationTableName = name, BulkCopyTimeout = 5 };
using var cancellation = new CancellationTokenSource();
var pending = Task.Run(() => copy.WriteToServerAsync(input, cancellation.Token));
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(10));
using var wait = new SqlCommand("SELECT COUNT(*) FROM sys.dm_exec_requests WHERE session_id=@session AND blocking_session_id > 0", observer);
wait.Parameters.AddWithValue("@session", session);
while ((int)(await wait.ExecuteScalarAsync(deadline.Token))! == 0)
await Task.Delay(10, deadline.Token);
var watch = Stopwatch.StartNew();
cancellation.Cancel();
try { await pending; Console.WriteLine("completed"); }
catch (Exception error) { Console.WriteLine($"{watch.Elapsed.TotalSeconds:F2}s: {error.GetType().Name}: {error.Message}"); }
await blockingTransaction.RollbackAsync();
}
finally
{
using var drop = new SqlCommand($"DROP TABLE {name}", observer);
await drop.ExecuteNonQueryAsync();
}
Expected behavior
Canceling the token should abandon the blocked operation before BulkCopyTimeout, as documented for the cancellation-token overload.
Actual result with BulkCopyTimeout = 5:
5.01s: SqlException: Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Operation cancelled by user.
Further technical details
- Microsoft.Data.SqlClient: 7.1.1
- .NET SDK: 10.0.403; target net10.0; Windows x64 client
- SQL Server: 2022 CU14,
mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04, Linux Docker container - Checked current main and the 7.1 release notes; the bulk-completion path still calls synchronous
RunParser()without the operation token. - Pinned source: CopyBatchesAsyncContinuedOnSuccess.
Please make the cancellation token interrupt this blocked server-read phase.
Found while implementing transaction enlistment in Relayer #290.
- 主要言語
- C#
- スター
- 1k
- フォーク
- 352
- 平均マージ
- 3日 21時間
- マージ済み PR(30日)
- 112
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
dotnet/SqlClient のほかの issue
-
Sync ExecuteReader on an STA thread deadlocks against a StateChange handler that marshals to the UI thread (OpenAsyncRetry path)再び着手できるかも @mdaigle が 49 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンExternal :link:
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
dotnet/SqlClient#4534 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 2 日以内に返信
-
Area\Tests
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
dotnet/SqlClient#4377 · コメント 2 件 ·
メンテナーはふだん 2 日以内に返信
-
Server error tokens are lost when SQL Server aborts the connection before the driver has posted its read (bare transport error instead of the server's error)対応中かも @poizan42 が 2 日前に担当しました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 25/100
dotnet/SqlClient#4994 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
A major performance regression from when you upgraded 5.2.2 to 7.1.1対応中かも @cheenamalhotra が 3 日前に担当しました。 オープンPerformance :chart_with_upwards_trend: Regression :boom:
dotnet/SqlClient#4985 · コメント 3 件 · 担当者 1 名 ·
メンテナーはふだん 2 日以内に返信
-
SqlTransaction.Dispose() throws NullReferenceException from SqlInternalTransaction.Rollback() when it races the background close of a broken connection対応中かも @MaceWindu が 3 日前に担当しました。 オープン
dotnet/SqlClient#4971 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 2 日以内に返信
dotnet/SqlClient の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
flyingpie/windows-terminal-quake#419 ·
メンテナーはふだん 1 日以内に返信
-
enhancement help wanted
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
-
a-corriger documentation
難易度 1/5 1〜3時間 初心者へのやさしさ 83/100
christian-raj/S-Aloha#272 ·
-
Incorrect loop bound in SizeOptIListSelectIterator.ToList対応中かも @equmagg が今日担当しました。 オープンarea-System.Collections untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
dotnet/runtime#137126 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug debugger flaky small
難易度 2/5 1〜3時間 初心者へのやさしさ 77/100
メンテナーはふだん 1 日以内に返信