From 03c460e6aa214dc66a19d1838856a340b229f2ea Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Fri, 15 Oct 2021 14:57:28 -0700 Subject: [PATCH] Revert "Fix Async Cancel (#956)" + Test Added for failed usecase --- .../Microsoft/Data/SqlClient/SqlCommand.cs | 51 +++++---- .../Data/SqlClient/TdsParserStateObject.cs | 96 +++++++++-------- .../Microsoft/Data/SqlClient/SqlCommand.cs | 51 +++++---- .../Data/SqlClient/TdsParserStateObject.cs | 101 ++++++++++-------- .../SQL/ParameterTest/ParametersTest.cs | 70 +++++++++++- .../SQL/SqlCommand/SqlCommandCancelTest.cs | 64 ----------- 6 files changed, 241 insertions(+), 192 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index b1c60be2f8..85fe6156ab 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -1450,13 +1450,19 @@ public int EndExecuteNonQueryAsync(IAsyncResult asyncResult) else { ThrowIfReconnectionHasBeenCanceled(); - if (!_internalEndExecuteInitiated && _stateObj != null) + // lock on _stateObj prevents races with close/cancel. + // If we have already initiate the End call internally, we have already done that, so no point doing it again. + if (!_internalEndExecuteInitiated) { - // call SetCancelStateClosed on the stateobject to ensure that cancel cannot - // happen after we have changed started the end processing - _stateObj.SetCancelStateClosed(); + lock (_stateObj) + { + return EndExecuteNonQueryInternal(asyncResult); + } + } + else + { + return EndExecuteNonQueryInternal(asyncResult); } - return EndExecuteNonQueryInternal(asyncResult); } } @@ -1865,15 +1871,19 @@ private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult) else { ThrowIfReconnectionHasBeenCanceled(); - - if (!_internalEndExecuteInitiated && _stateObj != null) + // lock on _stateObj prevents races with close/cancel. + // If we have already initiate the End call internally, we have already done that, so no point doing it again. + if (!_internalEndExecuteInitiated) { - // call SetCancelStateClosed on the stateobject to ensure that cancel cannot - // happen after we have changed started the end processing - _stateObj.SetCancelStateClosed(); + lock (_stateObj) + { + return EndExecuteXmlReaderInternal(asyncResult); + } + } + else + { + return EndExecuteXmlReaderInternal(asyncResult); } - - return EndExecuteXmlReaderInternal(asyncResult); } } @@ -2059,15 +2069,18 @@ internal SqlDataReader EndExecuteReaderAsync(IAsyncResult asyncResult) else { ThrowIfReconnectionHasBeenCanceled(); - - if (!_internalEndExecuteInitiated && _stateObj != null) + // lock on _stateObj prevents races with close/cancel. + if (!_internalEndExecuteInitiated) + { + lock (_stateObj) + { + return EndExecuteReaderInternal(asyncResult); + } + } + else { - // call SetCancelStateClosed on the stateobject to ensure that cancel cannot happen after - // we have changed started the end processing - _stateObj.SetCancelStateClosed(); + return EndExecuteReaderInternal(asyncResult); } - - return EndExecuteReaderInternal(asyncResult); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 077dd689cc..7e06c3df98 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -162,18 +162,10 @@ public TimeoutState(int value) // 2) post first packet write, but before session return - a call to cancel will send an // attention to the server // 3) post session close - no attention is allowed + private bool _cancelled; private const int _waitForCancellationLockPollTimeout = 100; private WeakReference _cancellationOwner = new WeakReference(null); - private static class CancelState - { - public const int Unset = 0; - public const int Closed = 1; - public const int Cancelled = 2; - } - - private int _cancelState; - // Cache the transaction for which this command was executed so upon completion we can // decrement the appropriate result count. internal SqlInternalTransaction _executedUnderTransaction; @@ -631,11 +623,6 @@ internal void Activate(object owner) Debug.Assert(result == 1, "invalid deactivate count"); } - internal bool SetCancelStateClosed() - { - return Interlocked.CompareExchange(ref _cancelState, CancelState.Closed, CancelState.Unset) == CancelState.Unset && _cancelState == CancelState.Closed; - } - // This method is only called by the command or datareader as a result of a user initiated // cancel request. internal void Cancel(object caller) @@ -643,38 +630,61 @@ internal void Cancel(object caller) Debug.Assert(caller != null, "Null caller for Cancel!"); Debug.Assert(caller is SqlCommand || caller is SqlDataReader, "Calling API with invalid caller type: " + caller.GetType()); - // only change state if it is Unset, so don't check the return value - Interlocked.CompareExchange(ref _cancelState, CancelState.Cancelled, CancelState.Unset); - - if ((_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken) - && (_cancellationOwner.Target == caller) && HasPendingData && !_attentionSent) + bool hasLock = false; + try { - bool hasParserLock = false; - // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks - while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks + while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) { - try - { - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: _waitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); - if (hasParserLock) - { - _parser.Connection.ThreadHasParserLockForClose = true; - SendAttention(); - } - } - finally - { - if (hasParserLock) + Monitor.TryEnter(this, _waitForCancellationLockPollTimeout, ref hasLock); + if (hasLock) + { // Lock for the time being - since we need to synchronize the attention send. + // This lock is also protecting against concurrent close and async continuations + + // Ensure that, once we have the lock, that we are still the owner + if ((!_cancelled) && (_cancellationOwner.Target == caller)) { - if (_parser.Connection.ThreadHasParserLockForClose) + _cancelled = true; + + if (HasPendingData && !_attentionSent) { - _parser.Connection.ThreadHasParserLockForClose = false; + bool hasParserLock = false; + // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks + while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + { + try + { + _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: _waitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); + if (hasParserLock) + { + _parser.Connection.ThreadHasParserLockForClose = true; + SendAttention(); + } + } + finally + { + if (hasParserLock) + { + if (_parser.Connection.ThreadHasParserLockForClose) + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + _parser.Connection._parserLock.Release(); + } + } + } } - _parser.Connection._parserLock.Release(); } } } } + finally + { + if (hasLock) + { + Monitor.Exit(this); + } + } } // CancelRequest - use to cancel while writing a request to the server @@ -761,7 +771,7 @@ private void ResetCancelAndProcessAttention() lock (this) { // Reset cancel state. - _cancelState = CancelState.Unset; + _cancelled = false; _cancellationOwner.Target = null; if (_attentionSent) @@ -983,10 +993,10 @@ internal Task ExecuteFlush() { lock (this) { - if (_cancelState != CancelState.Unset && 1 == _outputPacketNumber) + if (_cancelled && 1 == _outputPacketNumber) { ResetBuffer(); - _cancelState = CancelState.Unset; + _cancelled = false; throw SQL.OperationCancelled(); } else @@ -3344,7 +3354,7 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) byte packetNumber = _outputPacketNumber; // Set Status byte based whether this is end of message or not - bool willCancel = (_cancelState != CancelState.Unset) && (_parser._asyncWrite); + bool willCancel = (_cancelled) && (_parser._asyncWrite); if (willCancel) { status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; @@ -3392,7 +3402,7 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) private void CancelWritePacket() { - Debug.Assert(_cancelState != CancelState.Unset, "Should not call CancelWritePacket if _cancelled is not set"); + Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock try @@ -3978,7 +3988,7 @@ internal void AssertStateIsClean() Debug.Assert(_delayedWriteAsyncCallbackException == null, "StateObj has an unobserved exceptions from an async write"); // Attention\Cancellation\Timeouts Debug.Assert(!HasReceivedAttention && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {HasReceivedAttention}, Sending: {_attentionSending}"); - Debug.Assert(_cancelState == CancelState.Unset, "StateObj still has cancellation set"); + Debug.Assert(!_cancelled, "StateObj still has cancellation set"); Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); // Errors and Warnings Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index 75200e6916..35f5353f89 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -1779,13 +1779,19 @@ private int EndExecuteNonQueryAsync(IAsyncResult asyncResult) else { ThrowIfReconnectionHasBeenCanceled(); - if (!_internalEndExecuteInitiated && _stateObj != null) + // lock on _stateObj prevents races with close/cancel. + // If we have already initiate the End call internally, we have already done that, so no point doing it again. + if (!_internalEndExecuteInitiated) { - // call SetCancelStateClosed on the stateobject to ensure that cancel cannot - // happen after we have changed started the end processing - _stateObj.SetCancelStateClosed(); + lock (_stateObj) + { + return EndExecuteNonQueryInternal(asyncResult); + } + } + else + { + return EndExecuteNonQueryInternal(asyncResult); } - return EndExecuteNonQueryInternal(asyncResult); } } @@ -2293,14 +2299,19 @@ private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult) else { ThrowIfReconnectionHasBeenCanceled(); - if (!_internalEndExecuteInitiated && _stateObj != null) + // lock on _stateObj prevents races with close/cancel. + // If we have already initiate the End call internally, we have already done that, so no point doing it again. + if (!_internalEndExecuteInitiated) { - // call SetCancelStateClosed on the stateobject to ensure that cancel cannot - // happen after we have changed started the end processing - _stateObj.SetCancelStateClosed(); + lock (_stateObj) + { + return EndExecuteXmlReaderInternal(asyncResult); + } + } + else + { + return EndExecuteXmlReaderInternal(asyncResult); } - - return EndExecuteXmlReaderInternal(asyncResult); } } @@ -2547,15 +2558,19 @@ private SqlDataReader EndExecuteReaderAsync(IAsyncResult asyncResult) else { ThrowIfReconnectionHasBeenCanceled(); - - if (!_internalEndExecuteInitiated && _stateObj != null) + // lock on _stateObj prevents races with close/cancel. + // If we have already initiate the End call internally, we have already done that, so no point doing it again. + if (!_internalEndExecuteInitiated) + { + lock (_stateObj) + { + return EndExecuteReaderInternal(asyncResult); + } + } + else { - // call SetCancelStateClosed on the stateobject to ensure that cancel cannot happen after - // we have changed started the end processing - _stateObj.SetCancelStateClosed(); + return EndExecuteReaderInternal(asyncResult); } - - return EndExecuteReaderInternal(asyncResult); } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 43fc15b6d9..6f8c1c8776 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -153,17 +153,9 @@ internal int ObjectID // 2) post first packet write, but before session return - a call to cancel will send an // attention to the server // 3) post session close - no attention is allowed + private bool _cancelled; private const int _waitForCancellationLockPollTimeout = 100; - private static class CancelState - { - public const int Unset = 0; - public const int Closed = 1; - public const int Cancelled = 2; - } - - private int _cancelState; - // This variable is used to prevent sending an attention by another thread that is not the // current owner of the stateObj. I currently do not know how this can happen. Mark added // the code but does not remember either. At some point, we need to research killing this @@ -652,49 +644,68 @@ internal void Activate(object owner) Debug.Assert(result == 1, "invalid deactivate count"); } - internal bool SetCancelStateClosed() - { - return Interlocked.CompareExchange(ref _cancelState, CancelState.Closed, CancelState.Unset) == CancelState.Unset && _cancelState == CancelState.Closed; - } - // This method is only called by the command or datareader as a result of a user initiated // cancel request. internal void Cancel(int objectID) { - // only change state if it is Unset, so don't check the return value - Interlocked.CompareExchange(ref _cancelState, CancelState.Cancelled, CancelState.Unset); - - // don't allow objectID -1 since it is reserved for 'not associated with a command' - // yes, the 2^32-1 comand won't cancel - but it also won't cancel when we don't want it - if ((_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken) - && (objectID == _allowObjectID) && (objectID != -1) && _pendingData && !_attentionSent) + bool hasLock = false; + try { - bool hasParserLock = false; - // Keep looping until we have the parser lock (and so are allowed to write), or the conneciton closes\breaks - while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks + while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) { - try - { - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: _waitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); - if (hasParserLock) - { - _parser.Connection.ThreadHasParserLockForClose = true; - SendAttention(); - } - } - finally - { - if (hasParserLock) + + Monitor.TryEnter(this, _waitForCancellationLockPollTimeout, ref hasLock); + if (hasLock) + { // Lock for the time being - since we need to synchronize the attention send. + // At some point in the future, I hope to remove this. + // This lock is also protecting against concurrent close and async continuations + + // don't allow objectID -1 since it is reserved for 'not associated with a command' + // yes, the 2^32-1 comand won't cancel - but it also won't cancel when we don't want it + if ((!_cancelled) && (objectID == _allowObjectID) && (objectID != -1)) { - if (_parser.Connection.ThreadHasParserLockForClose) + _cancelled = true; + + if (_pendingData && !_attentionSent) { - _parser.Connection.ThreadHasParserLockForClose = false; + bool hasParserLock = false; + // Keep looping until we have the parser lock (and so are allowed to write), or the conneciton closes\breaks + while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + { + try + { + _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: _waitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); + if (hasParserLock) + { + _parser.Connection.ThreadHasParserLockForClose = true; + SendAttention(); + } + } + finally + { + if (hasParserLock) + { + if (_parser.Connection.ThreadHasParserLockForClose) + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + _parser.Connection._parserLock.Release(); + } + } + } } - _parser.Connection._parserLock.Release(); } } } } + finally + { + if (hasLock) + { + Monitor.Exit(this); + } + } } // CancelRequest - use to cancel while writing a request to the server @@ -787,7 +798,7 @@ private void ResetCancelAndProcessAttention() lock (this) { // Reset cancel state. - _cancelState = CancelState.Unset; + _cancelled = false; _allowObjectID = -1; if (_attentionSent) @@ -1091,10 +1102,10 @@ internal Task ExecuteFlush() { lock (this) { - if (_cancelState != CancelState.Unset && 1 == _outputPacketNumber) + if (_cancelled && 1 == _outputPacketNumber) { ResetBuffer(); - _cancelState = CancelState.Unset; + _cancelled = false; throw SQL.OperationCancelled(); } else @@ -3380,7 +3391,7 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) byte packetNumber = _outputPacketNumber; // Set Status byte based whether this is end of message or not - bool willCancel = (_cancelState != CancelState.Unset) && (_parser._asyncWrite); + bool willCancel = (_cancelled) && (_parser._asyncWrite); if (willCancel) { status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; @@ -3429,7 +3440,7 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) private void CancelWritePacket() { - Debug.Assert(_cancelState != CancelState.Unset, "Should not call CancelWritePacket if _cancelled is not set"); + Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock try @@ -4111,7 +4122,7 @@ internal void AssertStateIsClean() Debug.Assert(_delayedWriteAsyncCallbackException == null, "StateObj has an unobserved exceptions from an async write"); // Attention\Cancellation\Timeouts Debug.Assert(!_attentionReceived && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {_attentionReceived}, Sending: {_attentionSending}"); - Debug.Assert(_cancelState == CancelState.Unset, "StateObj still has cancellation set"); + Debug.Assert(!_cancelled, "StateObj still has cancellation set"); Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); // Errors and Warnings Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 3b176ef921..af54b4a4cc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; +using System.Threading; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -339,10 +340,10 @@ public static void TestParametersWithDatatablesTVPInsert() [InlineData("CAST(-0.0000000000000000000000000001 as decimal(38, 38))", "-0.0000000000000000000000000001")] public static void SqlDecimalConvertToDecimal_TestInRange(string sqlDecimalValue, string expectedDecimalValue) { - using(SqlConnection cnn = new(s_connString)) + using (SqlConnection cnn = new(s_connString)) { cnn.Open(); - using(SqlCommand cmd = new($"SELECT {sqlDecimalValue} val")) + using (SqlCommand cmd = new($"SELECT {sqlDecimalValue} val")) { cmd.Connection = cnn; using (SqlDataReader rdr = cmd.ExecuteReader()) @@ -645,7 +646,7 @@ private static void EnableOptimizedParameterBinding_NamesMustMatch() } Assert.NotNull(sqlException); - Assert.Contains("Must declare the scalar variable",sqlException.Message); + Assert.Contains("Must declare the scalar variable", sqlException.Message); Assert.Contains("@DoesNotExist", sqlException.Message); } } @@ -806,5 +807,68 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds() ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, dropSprocQuery); } } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void ClosedConnection_SqlParameterValueTest() + { + var threads = new List(); + for (int i = 0; i < 100; i++) + { + var t = new Thread(() => + { + for (int j = 0; j < 1000; j++) + { + try + { + RunParameterTest(); + } + catch (Exception e) + { + Assert.False(true, $"Unexpected exception occurred: {e.Message}"); + } + } + }); + t.Start(); + threads.Add(t); + } + for (int i = 0; i < threads.Count; i++) + { + threads[i].Join(); + } + } + + private static void RunParameterTest() + { + var cancellationToken = new CancellationTokenSource(50); + var expectedGuid = Guid.NewGuid(); + + using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); + connection.Open(); + using SqlCommand cm = connection.CreateCommand(); + cm.CommandType = CommandType.Text; + cm.CommandText = "select @id2 = @id;"; + cm.CommandTimeout = 2; + cm.Parameters.Add(new SqlParameter("@id", SqlDbType.UniqueIdentifier) { Value = expectedGuid }); + cm.Parameters.Add(new SqlParameter("@id2", SqlDbType.UniqueIdentifier) { Direction = ParameterDirection.Output }); + try + { + System.Threading.Tasks.Task task = cm.ExecuteNonQueryAsync(cancellationToken.Token); + task.Wait(); + } + catch (Exception) + { + //ignore cancellations + } + finally + { + connection.Close(); + } + if (cm.Parameters["@id2"].Value == null) + return; + else if ((Guid)cm.Parameters["@id2"].Value != expectedGuid) + { + Assert.False(true, "CRITICAL : Unexpected data found in SqlCommand parameters, this is a MAJOR issue."); + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs index 18dde97c6c..601bffa42a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs @@ -221,21 +221,6 @@ public static void AsyncCancelDoesNotWaitNP() AsyncCancelDoesNotWait(np_connStr).Wait(); } - // Synapse: WAITFOR not supported + ';' not supported. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public static void AsyncCancelDoesNotWait2() - { - AsyncCancelDoesNotWait2(tcp_connStr); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - [PlatformSpecific(TestPlatforms.Windows)] - public static void AsyncCancelDoesNotWaitNP2() - { - AsyncCancelDoesNotWait2(np_connStr); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void TCPAttentionPacketTestTransaction() { @@ -573,54 +558,5 @@ private static async Task AsyncCancelDoesNotWait(string connStr) Assert.InRange((ended - started).TotalSeconds, cancelSeconds, delaySeconds - 1); } } - - private static void AsyncCancelDoesNotWait2(string connStr) - { - const int delaySeconds = 30; - const int cancelSeconds = 1; - - var cancellationTokenSource = new CancellationTokenSource(); - DateTime started = DateTime.UtcNow; - DateTime ended = DateTime.UtcNow; - Exception exception = null; - - Task executing = ExecuteWaitForAsync(cancellationTokenSource.Token, connStr, delaySeconds); - - cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(cancelSeconds+1)); - - try - { - executing.Wait(); - } - catch (Exception ex) - { - exception = ex; - } - ended = DateTime.UtcNow; - - Assert.NotNull(exception); - Assert.IsType(exception); - Assert.NotNull(exception.InnerException); - Assert.IsType(exception.InnerException); - Assert.Contains("Operation cancelled by user.", exception.InnerException.Message); - Assert.InRange((ended - started).TotalSeconds, cancelSeconds, delaySeconds - 1); - } - - private static async Task ExecuteWaitForAsync(CancellationToken cancellationToken, string connectionString, int delaySeconds) - { - using (var connection = new SqlConnection(connectionString)) - { - await connection.OpenAsync().ConfigureAwait(false); - using (var command = new SqlCommand(@" -WHILE 1 = 1 -BEGIN - DECLARE @x INT = 1 -END", connection)) - { - command.CommandTimeout = delaySeconds + 10; - await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); - } - } - } } }