diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 0931d57557..9c406ad568 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -2756,6 +2756,12 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio ushort status; int count; + // This is added back since removing it from here introduces regressions in Managed SNI. + // It forces SqlDataReader.ReadAsync() method to run synchronously, + // and will block the calling thread until data is fed from SQL Server. + // TODO Investigate better solution to support non-blocking ReadAsync(). + stateObj._syncOverAsync = true; + // status // command // rowcount (valid only if DONE_COUNT bit is set) @@ -2855,11 +2861,10 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio } } - // _attentionSent set by 'SendAttention' - // _pendingData set by e.g. 'TdsExecuteSQLBatch' - // _hasOpenResult always set to true by 'WriteMarsHeader' + // HasPendingData set by e.g. 'TdsExecuteSQLBatch' + // HasOpenResult always set to true by 'WriteMarsHeader' // - if (!stateObj._attentionSent && !stateObj.HasPendingData && stateObj.HasOpenResult) + if (!stateObj.HasPendingData && stateObj.HasOpenResult) { /* Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 165e3eb4bb..1c906042f1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -3324,11 +3324,10 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio } } - // _attentionSent set by 'SendAttention' // _pendingData set by e.g. 'TdsExecuteSQLBatch' // _hasOpenResult always set to true by 'WriteMarsHeader' // - if (!stateObj._attentionSent && !stateObj._pendingData && stateObj._hasOpenResult) + if (!stateObj._pendingData && stateObj._hasOpenResult) { /* Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || 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 07fcf17721..b6f02b6f9d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs @@ -120,6 +120,92 @@ public static void TimeOutDuringRead() TimeOutDuringRead(s_connStr); } + [CheckConnStrSetupFact] + public static void TCPAttentionPacketTestTransaction() + { + CancelFollowedByTransaction(s_connStr); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public static void TCPAttentionPacketTestAlerts() + { + CancelFollowedByAlert(s_connStr); + } + + private static void CancelFollowedByTransaction(string constr) + { + using (SqlConnection connection = new SqlConnection(constr)) + { + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = @"SELECT @@VERSION"; + using (var r = cmd.ExecuteReader()) + { + cmd.Cancel(); + } + } + using (SqlTransaction transaction = connection.BeginTransaction()) + { } + } + } + + private static void CancelFollowedByAlert(string constr) + { + var alertName = "myAlert" + Guid.NewGuid().ToString(); + // Since Alert conditions are randomly generated, + // we will retry on unexpected error messages to avoid collision in pipelines. + var n = new Random().Next(1, 100); + bool retry = true; + int retryAttempt = 0; + while (retry && retryAttempt < 3) + { + try + { + using (var conn = new SqlConnection(constr)) + { + conn.Open(); + using (SqlCommand cmd = conn.CreateCommand()) + { + cmd.CommandText = "SELECT @@VERSION"; + using (var reader = cmd.ExecuteReader()) + { + cmd.Cancel(); // Sends Attention + } + } + using (SqlCommand cmd = conn.CreateCommand()) + { + cmd.CommandText = $@"EXEC msdb.dbo.sp_add_alert @name=N'{alertName}', + @performance_condition = N'SQLServer:General Statistics|User Connections||>|{n}'"; + cmd.ExecuteNonQuery(); + cmd.CommandText = @"USE [msdb]"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $@"/****** Object: Alert [{alertName}] Script Date: {DateTime.Now} ******/ + IF EXISTS (SELECT name FROM msdb.dbo.sysalerts WHERE name = N'{alertName}') + EXEC msdb.dbo.sp_delete_alert @name=N'{alertName}'"; + cmd.ExecuteNonQuery(); + } + } + } + catch (Exception e) + { + if (retryAttempt >= 3 || e.Message.Contains("The transaction operation cannot be performed")) + { + Assert.False(true, $"Retry Attempt: {retryAttempt} | Unexpected Exception occurred: {e.Message}"); + } + else + { + retry = true; + retryAttempt++; + Console.WriteLine($"CancelFollowedByAlert Test retry attempt : {retryAttempt}"); + Thread.Sleep(500); + continue; + } + } + retry = false; + } + } + private static void MultiThreadedCancel(string constr, bool async) { using (SqlConnection con = new SqlConnection(constr))