Skip to content

Fix async cancellation failing to send TDS attention signal - #4435

Open
cheenamalhotra wants to merge 175 commits into
mainfrom
dev/cheena/fix-async-cancel-attention
Open

cheenamalhotra wants to merge 175 commits into
mainfrom
dev/cheena/fix-async-cancel-attention

Conversation

@cheenamalhotra

@cheenamalhotra cheenamalhotra commented Jul 9, 2026 •

Copy link
Copy Markdown
Member

Description

Fixes #4424
Fixes #44
Fixes #4691

When using CancellationToken with async operations like ExecuteReaderAsync/ExecuteNonQueryAsync, cancellation fails to send a TDS attention signal to SQL Server if the server is blocked (e.g., infinite WHILE loop, WAITFOR DELAY, or partial results from RAISERROR WITH NOWAIT followed 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) held lock(_stateObj) while calling into EndExecute*Internal. When the server hadn't yet sent result metadata (blocked on a long-running or infinite query), TryConsumeMetaData performed a synchronous network read (_syncOverAsync = true), blocking the thread while holding the monitor lock.

Meanwhile, stateObj.Cancel() uses Monitor.TryEnter(this, 100ms) in a polling loop on the same stateObj instance. Since the lock was held by EndExecute*Async for 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:

  • EndExecuteReaderAsync
  • EndExecuteNonQueryAsync
  • EndExecuteXmlReaderAsync

The lock is unnecessary in these paths because concurrent access is already handled by:

  1. Cancel() — uses polling Monitor.TryEnter with parser state guards in its loop
  2. Connection close — detected by TryRun checking parser Broken/Closed state
  3. StateObj internals — handle their own synchronization for read/write operations

Also remove lock(_stateObj) from CreateLocalCompletionTask (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. localCompletion is completed from BeginExecuteReaderInternalReadStage -> _stateObj.ReadSni(completion), which resolves as soon as a packet arrives. And FinishExecuteReader only early-returns on the _internalEndExecuteInitiated fast path when !isInternal, so the internal call falls through to a blocking TryRun(RunBehavior.UntilDone, ...). Holding the monitor across that read starves TdsParserStateObject.Cancel(), which polls Monitor.TryEnter for the same monitor to send attention.

Commit ac8acd72 had reverted this because AE TestCancellationToken failed. The exception on the unlocked path is a SqlException with Number == 0 and two errors (A severe error occurred on the current command. then Operation cancelled by user.) — a correct cancellation, where the first error is what TryRun adds on consuming the attention ack. The test asserted an exact whole-message match, so that assertion is relaxed to Assert.Contains rather than reverting the fix.

Testing

Which tests fail without the source fix?

Measured per-test against a live SQL Server, not inferred:

Test Fails on main? Evidence
CancellationSendsAttention_WhenPartialResultsReceived Yes RAISERROR ... WITH NOWAIT flushes, WAITFOR blocks; reader returned instead of cancelling
CancellationOfInfiniteWhileLoop_DoesNotHang Yes loop streams DONE tokens (~30k server writes in 6s), so the End path runs and blocks; cts.Cancel() never returns
CancellationOnInternalEndExecutePath_SendsAttention Yes reader returned 60005ms after cancellation was requested
CancellationDuringExecuteXmlReaderAsync_SendsAttention Yes reader returned 60014ms after cancellation was requested
CancellationDuringExecuteReaderAsync_SendsAttention No WAITFOR first, nothing flushed, End path never runs — genuine guard test

DataReaderCancellationTest totals: 4 failed / 3 passed on main; 7 passed in 1s with the fix.

Note on the infinite WHILE case: it is not merely slow on main. The token callback runs SqlCommand.Cancel() synchronously on the cancelling thread, so CancellationTokenSource.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:

Fixed — Fixed asynchronous cancellation failing to send a TDS attention signal when the server had already flushed part of a response, which caused CancellationToken cancellation of ExecuteReaderAsync, ExecuteNonQueryAsync and ExecuteXmlReaderAsync to be ignored until the command completed on its own. In the worst case CancellationTokenSource.Cancel() itself would block indefinitely. (#4424, #4435)

  • Added manual test for partial results scenario (severity 10 RAISERROR WITH NOWAIT + WAITFOR + CancellationToken) — fails pre-fix
  • Added manual test for cancellation during ExecuteReaderAsync (WAITFOR as first statement)
  • Added manual test for the infinite WHILE loop repro from Canceling SQL Server query with while loop hangs forever #44 — fails pre-fix
  • Added AE manual test exercising EndExecuteReaderAsync with AE-enabled commands (cache warmup + parameterized WAITFOR delay)
  • Added CancellationOnInternalEndExecutePath_SendsAttention (forces the internal-end path via the DEBUG _forceInternalEndQuery hook, so it runs without an AE setup and skips against a Release driver) — fails pre-fix
  • Added CancellationDuringExecuteXmlReaderAsync_SendsAttention, closing the ExecuteXmlReaderAsync coverage gap (FOR XML RAW; FOR XML AUTO fails at compile time before RAISERROR runs) — fails pre-fix
  • Relaxed AE TestCancellationToken exact-message assertion to Assert.Contains (attention ack adds a leading error)
  • Observed the abandoned execTask on the watchdog branch to avoid unobserved task exceptions leaking into unrelated tests
  • Cancellation in the partial-results test is synchronized on InfoMessage arrival (asserted), not a fixed timer
  • All existing unit tests pass (904/908; 4 pre-existing failures unrelated to this change)
  • Existing AE cancellation tests (TestSqlCommandCancellationToken) pass
  • Public API unchanged — no ref assembly updates needed
  • Verified against customer repro

Changes

File Change
SqlCommand.Reader.cs Removed lock(_stateObj) in EndExecuteReaderAsync
SqlCommand.NonQuery.cs Removed lock(_stateObj) in EndExecuteNonQueryAsync
SqlCommand.Xml.cs Removed lock(_stateObj) in EndExecuteXmlReaderAsync
SqlCommand.cs Removed lock(_stateObj) in CreateLocalCompletionTask (internal-end path)
DataReaderCancellationTest.cs Added CancellationSendsAttention_WhenPartialResultsReceived, CancellationDuringExecuteReaderAsync_SendsAttention, CancellationOfInfiniteWhileLoop_DoesNotHang, CancellationOnInternalEndExecutePath_SendsAttention and CancellationDuringExecuteXmlReaderAsync_SendsAttention tests
CommandHelper.cs / DataTestUtility.cs Exposed the DEBUG _forceInternalEndQuery hook so the internal-end path can be tested without an AE setup
ApiShould.cs Added TestAsyncCancellationSendsAttention_WithAlwaysEncryptedCommand test

cheenamalhotra and others added 2 commits July 8, 2026 20:14
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>
Copilot AI lite review requested due to automatic review settings July 9, 2026 06:09
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Jul 9, 2026
@cheenamalhotra cheenamalhotra added this to the 7.1.0-preview3 milestone Jul 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) from EndExecuteReaderAsync, EndExecuteNonQueryAsync, and EndExecuteXmlReaderAsync to 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>
Copilot AI review requested due to automatic review settings July 9, 2026 06:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

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>
Copilot AI review requested due to automatic review settings July 9, 2026 06:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

- 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>
Copilot AI review requested due to automatic review settings July 9, 2026 06:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 9, 2026 06:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

- 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>
Copilot AI review requested due to automatic review settings July 9, 2026 07:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@cheenamalhotra
cheenamalhotra marked this pull request as ready for review July 9, 2026 07:12
@cheenamalhotra
cheenamalhotra requested a review from a team as a code owner July 9, 2026 07:12
paulmedynski and others added 19 commits November 10, 2026 09:04
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>
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>
Copilot AI lite review requested due to automatic review settings November 13, 2026 09:26
@github-actions github-actions Bot added the Localization Needed PRs that contain changes that would need localization label Nov 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Approval is blocked by active moderate findings: several cancellation tests may cancel before reaching the server, and multiple connection setups can fail under Azure SQL authentication.

2 open issues

Review effort: Lite

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>
Copilot AI lite review requested due to automatic review settings November 13, 2026 09:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A moderate finding remains because the test bypasses the shared connection factory, which can cause supported Azure SQL configurations to fail before exercising the cancellation regression.

2 open issues

Review effort: Lite

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>
Copilot AI lite review requested due to automatic review settings November 13, 2026 10:23

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author attention needed PRs that require author to respond or make updates to PR. Localization Needed PRs that contain changes that would need localization

Projects

Status: Waiting for customer