Repository navigation
Fix async cancellation failing to send TDS attention signal - #4435
Open
cheenamalhotra wants to merge 175 commits into
Open
cheenamalhotra wants to merge 175 commits into
cheenamalhotra wants to merge 175 commits into
Conversation
Remove lock(_stateObj) from EndExecuteReaderAsync, EndExecuteNonQueryAsync, and EndExecuteXmlReaderAsync. The lock prevented Cancel() from acquiring the stateObj monitor to send a TDS attention signal when the async completion path was blocked on a synchronous network read (e.g., waiting for metadata during WAITFOR). This caused cancellation via CancellationToken to hang until the query completed naturally. Concurrent close/cancel safety is maintained by: - Parser state checks within TryRun (detects Broken/Closed state) - Cancel()'s polling loop via Monitor.TryEnter with parser state guards - The stateObj's internal synchronization mechanisms Fixes #4424 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Validates that CancellationToken triggers TDS attention when the server has sent partial results (RAISERROR WITH NOWAIT) but is blocked on WAITFOR. This test would previously hang for 60+ seconds without the fix. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a deadlock/hang in async cancellation by removing lock(_stateObj) from the async EndExecute* paths so that TdsParserStateObject.Cancel() can acquire the monitor and send a TDS attention signal even when the async end-path is blocked on synchronous network reads (e.g., partial results + WAITFOR).
Changes:
- Removed
lock(_stateObj)fromEndExecuteReaderAsync,EndExecuteNonQueryAsync, andEndExecuteXmlReaderAsyncto avoid blocking cancellation’s attention-send path. - Added a new manual regression test intended to reproduce the “partial results + server blocked” cancellation hang scenario from #4424.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs | Removes lock(_stateObj) from EndExecuteReaderAsync and documents rationale tied to #4424. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs | Removes lock(_stateObj) from EndExecuteNonQueryAsync to keep cancellation from being blocked by the monitor. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs | Removes lock(_stateObj) from EndExecuteXmlReaderAsync consistent with reader/nonquery. |
| src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderCancellationTest.cs | Adds a manual regression test for cancellation sending attention after partial results are received. |
…pt SqlException Move CancelAfter() before ExecuteReaderAsync so cancellation fires during the async completion path (not just ReadAsync). Also accept SqlException in addition to OperationCanceledException since attention acknowledgment from the server surfaces as SqlException. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds TestAsyncCancellationSendsAttention_WithAlwaysEncryptedCommand that exercises the internal-end path in CreateLocalCompletionTask by warming the query metadata cache first, then running a long-running AE query with CancellationToken. Also removes the lock from CreateLocalCompletionTask to fix the same contention issue in the AE retry path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Use severity 10 in RAISERROR (matches real-world repro from #4424) - Assert.Fail if reader is returned in WAITFOR-first test (should never happen) - Fix AE test cache key: use same CommandText with parameterized @delay so warmup and cancel executions share the metadata cache entry Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Assert cts.IsCancellationRequested in all tests to guard against false positives from unrelated SqlExceptions - Reorder AE test query to WAITFOR DELAY @delay; SELECT ... so that EndExecuteReaderInternal blocks on metadata (exercises the CreateLocalCompletionTask internal-end path as intended) - Add Assert.Fail in AE test if reader is unexpectedly returned Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
cheenamalhotra
marked this pull request as ready for review
July 9, 2026 07:12
Retain lifecycle quarantine and add probation for the startup/close repair. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve tool-local packages and source mappings while aligning published dependency restores with the repository's governed feed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Perf | Reduce SqlBulkCopy metadata query cost (#4985) - Skip the #Column_Aliases temp table and result set unless mappings use SQL Graph aliases. - Add Switch.Microsoft.Data.SqlClient.UseLegacyBulkCopyMetadataDiscovery (default false) to restore the pre-6.0 SET FMTONLY ON / SELECT * query. Graph alias mappings still use the catalog-based query. - Add unit tests for query generation and manual tests for the legacy path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Perf | Rename bulk copy switch to EnableBulkCopyCatalogMetadataDiscovery (#4985) Default false uses the pre-6.0 FMTONLY query. Setting it to true enables the sys.all_columns query for hidden columns and SQL Graph aliases. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix | Declare bulk copy switch for all target frameworks Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Perf | Add automatic bulk copy catalog metadata fallback (#4985) Use a tri-state switch to preserve hidden-column and graph-alias support while keeping ordinary copies on the fast metadata path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Preserve bulk-copy ordinal mappings and cached alias metadata Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Correct bulk metadata integration test setup and statistics Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use active temporal metadata in hidden cache fallback tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Cover bulk-copy collations after hidden period columns Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix bulk-copy collation alignment after hidden columns Match collation rows by column name when fast metadata omits hidden columns. Preserve positional lookup for matching result shapes and scan mismatched rows once without additional server calls. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Add connection encryption info public API surface (Slice A) Adds SqlConnectionEncryptionInfo, SqlConnectionEncryptionMode, and SqlServerCertificateValidationErrors, plus SqlConnection.EncryptionInfo and SqlException.EncryptionInfo getters. In this slice both getters always return an empty snapshot; no TdsParser/SNI plumbing yet. Related to #2353. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use SHA-256 certificate hash in SqlConnectionEncryptionInfo; add design note Replace PresentedServerCertificateThumbprint (string?) with PresentedServerCertificateHash256 (ReadOnlyMemory<byte>?), per the API agreed with the SSMS team. The internal constructor validates the hash is 32 bytes and keeps a private copy. Add doc/design-notes/2353 for reviewers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix doc include path and slice plan in design note Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add NegotiatedCipherSuite to SqlConnectionEncryptionInfo Adds int? NegotiatedCipherSuite (IANA TLS cipher suite identifier selected by the server) to the public snapshot, ref assembly, XML docs, design note and unit tests. Proposal pending API review. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Cipher suite: NegotiatedCipherSuiteId (int?) on all TFMs; TlsCipherSuite? on .NET (review feedback) - Add NegotiatedCipherSuiteId (int?, CLS-compliant) on every target framework. It returns the IANA identifier unchanged, including unnamed/GREASE values. - On .NET only, NegotiatedCipherSuite exposes the same value as System.Net.Security.TlsCipherSuite? and is marked [CLSCompliant(false)]. - No NegotiatedCipherSuite member on net462/netstandard2.0, so the only shared member has an identical signature across the netstandard2.0 ref and the net10.0 implementation. - The internal constructor still takes int? (0..0xFFFF). The internal NegotiatedCipherSuiteValue is removed. - Update ref, XML docs, design note and unit tests. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Encryption info: replace certificate hash with GetServerCertificate() (threat model review) Remove PresentedServerCertificateHash256 and expose the server-presented leaf certificate via X509Certificate2? GetServerCertificate(). Each call returns a new caller-owned instance parsed from a private DER copy, or null when no certificate was captured or the bytes cannot be parsed. The internal ctor now takes byte[]? presentedServerCertificate, and PresentedServerCertificateRawData exposes the captured bytes internally. Update ref, XML docs, design note and unit tests. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Encryption info tests: register SqlErrorCollection known types for DataContract round trip Since #4622, SqlException.GetObjectData writes the error collection, so DataContractSerializer needs SqlErrorCollection, SqlError and List<object> as known types. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Encryption info: address Slice A review feedback - Sort SqlConnectionEncryptionInfo public members alphabetically in the implementation, reference assembly, XML doc snippets and design note. - Document how each SqlConnectionEncryptionMode value is negotiated and how much of the connection TLS covers. - Clarify that the internal PresentedServerCertificateRawData wraps the snapshot's private copy and must not be mutated. - Add XML docs to the test constants and helpers. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Docs: make SqlConnectionEncryptionInfo type remarks release-neutral Remove the statement that the driver never populates the snapshot; it becomes inaccurate once capture lands in later slices. Point to the getter remarks for when values are captured. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Docs: clarify negotiated protocol availability on failure Describe null as unavailable, not proof that no TLS handshake completed, and document that a protocol observed before a later handshake or connection failure is retained. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Docs: mark encryption info API approved Update the design note to show the approved API and remove outdated review caveats. Replace Slice A-only empty-snapshot wording in the getter remarks with the stable connection and exception snapshot contracts. Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c47d6846-090b-4240-9f75-efa50beb8e34
* Update compatible dependencies for SqlClient 8.0.0 Refresh shipping dependency versions and synchronize NuGet dependency floors while retaining supported framework bands. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use newly indexed dependency servicing releases Advance Azure.Core to 1.65.0, runtime dependencies to 10.0.15, and ApiCompat.Task to 10.0.404 after governed feed ingestion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Localize authentication errors in Abstractions Restore resource-backed authentication exception factories, neutral and satellite resources, resource embedding, fallback tests and driver resource cleanup as a separate stacked layer. Verify French parent-culture lookup alongside neutral messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Fix authentication localization review feedback in #4852 Restore complete ResX headers across all authentication resource cultures and explain culture selection and diagnostic fallback intent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Complete authentication exception localization in #4852 Move remaining configuration and registration exception messages into neutral resources, preserve framework exception messages, and focus string tests on helper behavior rather than translation availability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Cover authentication resource lookup and refine exception factories in #4852 Use French as the stable satellite for direct lookup, parent fallback, neutral fallback, and independent UI/formatting culture coverage. Return concrete exception types from authentication factories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Localize token validation and simplify authentication string tests Remove the unused WAM upgrade resources and factory, add the neutral access-token validation resource, and replace the exception-factory theory with focused tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Enable overall connect timeout for pool waits by default Preserve the explicit false compatibility opt-out, document migration behavior, and cover remaining-budget propagation for both pool implementations and acquisition paths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use milliseconds for background pool test timeout Give the warmup test its intended 15-second creation budget now that overall timeout enforcement honors the supplied timer. Preserve all application-attribution assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Add XML documentation validation to sqlclient-pr Share the XML documentation validator and tests between PR and OneBranch pipelines. Validate packaged documentation in parallel with tests using a retry-safe artifact handoff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix PR XML documentation validation and extension coverage Treat framework-owned SqlTriggerContext references as external without weakening checks for missing repository types. Validate all extension snippet directories through the shared step and add regression coverage for argument composition and public documentation references. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…4972) * Add tests for SqlTransaction.Dispose racing the close of a broken connection Each test breaks the connection while an async command is pending in a transaction. The break goes through ProxyServer, either as a timeout whose attention the server never acknowledges or as a network reset. The driver faults the command first and closes the connection afterwards on another thread. The tests hold that close at the internal connection's close lock. They dispose the transaction either before the close runs, or while it runs. A SqlClient scope-trace listener on the disposing thread places the second case between Dispose's null check and Rollback's dereference. On main, the dispose throws InvalidOperationException ("The connection is closed") or NullReferenceException from SqlInternalTransaction.Rollback(). * Fix SqlTransaction.Dispose racing the close of a broken connection The background close of a broken connection zombies the transaction and releases the parser. SqlInternalTransaction.Rollback() read _innerConnection several times without a lock, so a dispose that raced that close could throw. Rollback() now reads the connection once and returns if the transaction is already zombied. When disposing on a connection that is doomed, broken or closed, it returns without a rollback. Nothing can be sent there, and the server ends the transaction with the session. IsLockedForBulkCopy no longer dereferences a released parser. Fixes #4971 * Skip the transaction dispose race tests on Azure SQL and named instances The tests connect through ProxyServer, which rewrites the data source to 127.0.0.1. On Azure SQL and on the named-instance setup the server certificate never matches that name, so the connection failed in the pre-login handshake before any test code ran. Skip them there, as the other ProxyServer tests do. * Add a test for the connection breaking between Rollback's check and validation After an unacknowledged timeout, the attention-timeout callback faults the command's task and marks the parser broken a few lines later. A dispose started by the fault can pass Rollback()'s broken-connection check and then fail in ValidateConnectionForExecute, which still throws while disposing. Nothing observable runs in that window, so the test uses a new DEBUG-only hook, SqlInternalTransaction.s_afterRollbackConnectionCheck, following the existing test hooks in TdsParserStateObject. The test marks the parser broken from the hook on the disposing thread. On this commit the dispose throws InvalidOperationException ("The connection is closed"). Also unsubscribe BackgroundCloseGate's StateChange handler before its event is disposed. * Fix SqlTransaction.Dispose when the connection breaks before Rollback validates it If the connection breaks after Rollback() has checked it, the validation throws. While disposing, Rollback() now returns in that case too, for the same reason as the check: nothing can be sent, and the server ends the transaction with the session. An explicit Rollback() still throws.
* Address Slice A review feedback for connection encryption info Follow-up to #4830 addressing review feedback on the release/8.0 cherry-pick (#5037). - Carry TlsCipherSuite end-to-end on .NET: the internal constructor takes TlsCipherSuite? and NegotiatedCipherSuite is an auto-property; .NET Framework keeps the range-checked int? identifier. - Make SqlConnectionEncryptionMode byte-backed. - Catch only CryptographicException in GetServerCertificate. - Annotate GetServerCertificate as returning X509Certificate2? in the ref. - Clarify null semantics and validation/decoding behavior in the docs and drop implementation details. - Move SqlConnection and SqlException tests into SqlConnectionTests and SqlExceptionTests, share helpers via EncryptionInfoTestData, and tighten assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix notsupported build for nullable-annotated ref members GenAPI reproduces the '?' on SqlConnectionEncryptionInfo.GetServerCertificate() but emitted no '#nullable' context into the auto-generated source (CS8669) and copied the compiler-reserved NullableContextAttribute on net10.0 (CS8335). Pass a header file that adds '#nullable enable annotations', matching the ref assembly, and exclude the Nullable/NullableContext attributes from the output. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address second-round review feedback on Slice A follow-up - Take ushort in the cipher suite identifier theories so InlineData cannot exceed 16 bits. - Document why the BinaryFormatter SqlException tests are .NET Framework only. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Add release notes for SqlClient 7.1.2 and 7.0.4 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Refresh SNI minimums after release branch upgrades Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ntion' into dev/cheena/pr-4435-fixes
Keep partial responses open until ATTENTION completes the message, preserve packet numbering, and use interruptible listener acceptance. Split async API coverage and clarify regression comments. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| SELECT 1 AS Result;"; | ||
|
|
||
| using (var cts = new CancellationTokenSource()) | ||
| using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) |
|
|
||
| // Give the batch time to reach the server-side loop. This avoids the | ||
| // flakiness of cancelling before the query is actually in flight. | ||
| await Task.Delay(System.TimeSpan.FromMilliseconds(500)); |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Force early acknowledgement in the async cancellation regressions and accept an in-progress send in the parser's debug invariant. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
Fixes #4424
Fixes #44
Fixes #4691
When using
CancellationTokenwith async operations likeExecuteReaderAsync/ExecuteNonQueryAsync, cancellation fails to send a TDS attention signal to SQL Server if the server is blocked (e.g., infiniteWHILEloop,WAITFOR DELAY, or partial results fromRAISERROR WITH NOWAITfollowed by a blocking operation). The cancellation hangs until the query completes naturally — which may be forever for infinite loops.Root Cause
EndExecuteReaderAsync(and the equivalent NonQuery/XmlReader methods) heldlock(_stateObj)while calling intoEndExecute*Internal. When the server hadn't yet sent result metadata (blocked on a long-running or infinite query),TryConsumeMetaDataperformed a synchronous network read (_syncOverAsync = true), blocking the thread while holding the monitor lock.Meanwhile,
stateObj.Cancel()usesMonitor.TryEnter(this, 100ms)in a polling loop on the same stateObj instance. Since the lock was held byEndExecute*Asyncfor the entire duration of the server-side wait,Cancel()could never acquire the monitor and never sent the attention signal.Fix
Remove
lock(_stateObj)from the user-facing async end methods:EndExecuteReaderAsyncEndExecuteNonQueryAsyncEndExecuteXmlReaderAsyncThe lock is unnecessary in these paths because concurrent access is already handled by:
Monitor.TryEnterwith parser state guards in its loopTryRunchecking parserBroken/ClosedstateAlso remove
lock(_stateObj)fromCreateLocalCompletionTask(the internal-end/retry path used when column encryption is enabled and parameter metadata came from the cache).An earlier revision of this PR kept that lock and justified it on the grounds that the continuation runs only after the response is buffered. That was wrong.
localCompletionis completed fromBeginExecuteReaderInternalReadStage->_stateObj.ReadSni(completion), which resolves as soon as a packet arrives. AndFinishExecuteReaderonly early-returns on the_internalEndExecuteInitiatedfast path when!isInternal, so the internal call falls through to a blockingTryRun(RunBehavior.UntilDone, ...). Holding the monitor across that read starvesTdsParserStateObject.Cancel(), which pollsMonitor.TryEnterfor the same monitor to send attention.Commit
ac8acd72had reverted this because AETestCancellationTokenfailed. The exception on the unlocked path is aSqlExceptionwithNumber == 0and two errors (A severe error occurred on the current command.thenOperation cancelled by user.) — a correct cancellation, where the first error is whatTryRunadds on consuming the attention ack. The test asserted an exact whole-message match, so that assertion is relaxed toAssert.Containsrather than reverting the fix.Testing
Which tests fail without the source fix?
Measured per-test against a live SQL Server, not inferred:
main?CancellationSendsAttention_WhenPartialResultsReceivedRAISERROR ... WITH NOWAITflushes,WAITFORblocks; reader returned instead of cancellingCancellationOfInfiniteWhileLoop_DoesNotHangcts.Cancel()never returnsCancellationOnInternalEndExecutePath_SendsAttentionCancellationDuringExecuteXmlReaderAsync_SendsAttentionCancellationDuringExecuteReaderAsync_SendsAttentionWAITFORfirst, nothing flushed, End path never runs — genuine guard testDataReaderCancellationTesttotals: 4 failed / 3 passed onmain; 7 passed in 1s with the fix.Note on the infinite
WHILEcase: it is not merely slow onmain. The token callback runsSqlCommand.Cancel()synchronously on the cancelling thread, soCancellationTokenSource.Cancel()itself blocks indefinitely (observed >20 minutes). With the fix it returns in 4ms.Suggested release note entry
Release notes in this repo are batched into dedicated release PRs rather than added per bugfix, so no file is added here. Suggested entry:
WHILEloop repro from Canceling SQL Server query with while loop hangs forever #44 — fails pre-fixCancellationOnInternalEndExecutePath_SendsAttention(forces the internal-end path via the DEBUG_forceInternalEndQueryhook, so it runs without an AE setup and skips against a Release driver) — fails pre-fixCancellationDuringExecuteXmlReaderAsync_SendsAttention, closing theExecuteXmlReaderAsynccoverage gap (FOR XML RAW;FOR XML AUTOfails at compile time beforeRAISERRORruns) — fails pre-fixTestCancellationTokenexact-message assertion toAssert.Contains(attention ack adds a leading error)execTaskon the watchdog branch to avoid unobserved task exceptions leaking into unrelated testsInfoMessagearrival (asserted), not a fixed timerTestSqlCommandCancellationToken) passChanges
SqlCommand.Reader.cslock(_stateObj)inEndExecuteReaderAsyncSqlCommand.NonQuery.cslock(_stateObj)inEndExecuteNonQueryAsyncSqlCommand.Xml.cslock(_stateObj)inEndExecuteXmlReaderAsyncSqlCommand.cslock(_stateObj)inCreateLocalCompletionTask(internal-end path)DataReaderCancellationTest.csCancellationSendsAttention_WhenPartialResultsReceived,CancellationDuringExecuteReaderAsync_SendsAttention,CancellationOfInfiniteWhileLoop_DoesNotHang,CancellationOnInternalEndExecutePath_SendsAttentionandCancellationDuringExecuteXmlReaderAsync_SendsAttentiontestsCommandHelper.cs/DataTestUtility.cs_forceInternalEndQueryhook so the internal-end path can be tested without an AE setupApiShould.csTestAsyncCancellationSendsAttention_WithAlwaysEncryptedCommandtest