-
Notifications
You must be signed in to change notification settings - Fork 330
Tests: SNIClose close-during-async-IO regression tests (ADO #43847 / ICM 775308542) #4420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
paulmedynski
wants to merge
6
commits into
main
Choose a base branch
from
dev/paul/sni-close
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+755
−0
Open
Changes from 2 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3638a99
Add SNIClose deadlock regression tests (ADO #43847 / ICM 775308542)
paulmedynski 9f8f7de
Add SNIClose close-during-IO regression tests (pre-login, TLS handsha…
paulmedynski ad5297e
Address PR #4420 review: bound cleanup on deadlock + correct regressi…
paulmedynski 29aa65a
Refactor SNIClose tests to xUnit Theory (Close/Dispose via InlineData)
paulmedynski 988fac7
Address PR #4420 review: dedicated close thread + always-run test cle…
paulmedynski 88a1725
Fix false-positive risk in pre-login SNIClose deadlock test
paulmedynski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseDeadlockTest.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Data; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.ManualTesting.Tests | ||
| { | ||
| /// <summary> | ||
| /// Live-server regression test for ADO.Net work item 43847 / ICM 775308542: | ||
| /// "SNIClose can deadlock with in-flight async I/O during connection close". | ||
| /// | ||
| /// <para> | ||
| /// This is the MARS-enabled counterpart to the in-process | ||
| /// <c>SNICloseDeadlockTest</c> unit tests. The native <c>Dispose()</c> path | ||
| /// in <c>TdsParserStateObjectNative</c> carries a long-standing "UNDONE" | ||
| /// note about needing to block for pending callbacks on <b>logical</b> | ||
| /// (MARS) connections during close. The in-process TDS test server does not | ||
| /// support MARS, so exercising the SMUX logical-session close path requires | ||
| /// a real SQL Server. | ||
| /// </para> | ||
| /// | ||
| /// <para> | ||
| /// The test enables MARS and starts an asynchronous read of a batch that the | ||
| /// server deliberately withholds (<c>WAITFOR DELAY</c>). This guarantees the | ||
| /// client's async read is genuinely in flight on a MARS logical session at | ||
| /// the moment the connection is closed. The close is performed on a worker | ||
| /// thread under a bounded wait: a deadlock manifests as the wait timing out, | ||
| /// which fails the test. A healthy close aborts the in-flight command and | ||
| /// returns promptly. | ||
| /// </para> | ||
| /// </summary> | ||
| [Trait("Set", "1")] | ||
| public class MarsCloseDeadlockTest | ||
| { | ||
| /// <summary> | ||
| /// How long the server withholds the response. Must comfortably exceed | ||
| /// <see cref="CloseBudget"/> so that a prompt close is attributable to | ||
| /// the close path aborting the command, not to the query completing on | ||
| /// its own. | ||
| /// </summary> | ||
| private const string StallDelay = "00:00:30"; | ||
|
|
||
| /// <summary> | ||
| /// Upper bound for how long a non-deadlocked close should take. Close of | ||
| /// a connection with an in-flight command is effectively instantaneous; | ||
| /// this budget only distinguishes "completed" from "deadlocked" without | ||
| /// hanging the run on a slow agent. | ||
| /// </summary> | ||
| private static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(15); | ||
|
|
||
| /// <summary> | ||
| /// How long to wait to confirm the async read is genuinely pending (not | ||
| /// completed) before closing. Small relative to <see cref="StallDelay"/>. | ||
| /// </summary> | ||
| private static readonly TimeSpan PendingConfirmDelay = TimeSpan.FromSeconds(3); | ||
|
|
||
| [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] | ||
| [InlineData(false)] | ||
| [InlineData(true)] | ||
| public void CloseOrDispose_WithPendingMarsAsyncRead_DoesNotDeadlock(bool disposeInsteadOfClose) | ||
| { | ||
| // Enable MARS so the async read runs over a SMUX logical session, and | ||
| // disable pooling so Close()/Dispose() tears down the physical | ||
| // connection (reaching SNIClose) instead of returning it to the pool. | ||
| string connectionString = | ||
| new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) | ||
| { | ||
| MultipleActiveResultSets = true, | ||
| Pooling = false, | ||
| }.ConnectionString; | ||
|
|
||
| SqlConnection connection = new(connectionString); | ||
| connection.Open(); | ||
|
|
||
| SqlCommand command = | ||
| new($"WAITFOR DELAY '{StallDelay}'; SELECT 1;", connection); | ||
|
|
||
| // Start an async read that will pend: the server withholds the | ||
| // response for StallDelay, so this Task will not complete until we | ||
| // either wait out the delay or tear down the connection. | ||
| Task<SqlDataReader> readTask = command.ExecuteReaderAsync(); | ||
|
|
||
| // Confirm the read is genuinely in flight before we close: it must | ||
| // not have completed and the connection must still be open. | ||
| Assert.False( | ||
| readTask.Wait(PendingConfirmDelay), | ||
| "The async read completed before the server released the " + | ||
| "response; the read was not in flight at close time."); | ||
| Assert.Equal(ConnectionState.Open, connection.State); | ||
|
|
||
| // Close/dispose the connection on a worker thread while the MARS | ||
| // async read is in flight. This is the operation that can deadlock | ||
| // in SNIClose. | ||
| Task closeTask = Task.Run(() => | ||
| { | ||
| if (disposeInsteadOfClose) | ||
| { | ||
| connection.Dispose(); | ||
| } | ||
| else | ||
| { | ||
| connection.Close(); | ||
| } | ||
| }); | ||
|
paulmedynski marked this conversation as resolved.
Outdated
|
||
|
|
||
| bool closedInTime = closeTask.Wait(CloseBudget); | ||
|
|
||
| // Observe the read task so its (expected) failure is not unobserved. | ||
| try | ||
| { | ||
| readTask.Wait(CloseBudget); | ||
| } | ||
| catch | ||
| { | ||
| // The pending read is expected to fault or cancel once the | ||
| // connection is torn down. That is not what this test asserts. | ||
| } | ||
| finally | ||
| { | ||
| command.Dispose(); | ||
| connection.Dispose(); | ||
| } | ||
|
paulmedynski marked this conversation as resolved.
|
||
|
|
||
| Assert.True( | ||
| closedInTime, | ||
| $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not " + | ||
| $"complete within {CloseBudget.TotalSeconds:N0}s while a MARS " + | ||
| "async read was in flight. This indicates the SNIClose deadlock " + | ||
| "(ADO.Net #43847 / ICM 775308542)."); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.