Skip to content

Commit 101fafc

Browse files
Add Async API Tests for Timeout (#1)
* Add Async API Tests for Timeout
1 parent 9c57ae9 commit 101fafc

File tree

5 files changed

+267
-27
lines changed

5 files changed

+267
-27
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,14 @@ internal enum SnapshottedStateFlags : byte
125125
internal volatile bool _attentionSent; // true if we sent an Attention to the server
126126
internal volatile bool _attentionSending;
127127

128-
private readonly LastIOTimer _lastSuccessfulIOTimer;
129-
128+
// Below 2 properties are used to enforce timeout delays in code to
129+
// reproduce issues related to theadpool starvation and timeout delay.
130+
// It should always be set to false by default, and only be enabled during testing.
131+
internal bool _enforceTimeoutDelay = false;
132+
internal int _enforcedTimeoutDelayInMilliSeconds = 5000;
133+
134+
private readonly LastIOTimer _lastSuccessfulIOTimer;
135+
130136
// secure password information to be stored
131137
// At maximum number of secure string that need to be stored is two; one for login password and the other for new change password
132138
private SecureString[] _securePasswords = new SecureString[2] { null, null };
@@ -1455,7 +1461,7 @@ internal bool TryReadInt16(out short value)
14551461
{
14561462
// The entire int16 is in the packet and in the buffer, so just return it
14571463
// and take care of the counters.
1458-
buffer = _inBuff.AsSpan(_inBytesUsed,2);
1464+
buffer = _inBuff.AsSpan(_inBytesUsed, 2);
14591465
_inBytesUsed += 2;
14601466
_inBytesPacket -= 2;
14611467
}
@@ -1489,7 +1495,7 @@ internal bool TryReadInt32(out int value)
14891495
}
14901496

14911497
AssertValidState();
1492-
value = (buffer[3] << 24) + (buffer[2] <<16) + (buffer[1] << 8) + buffer[0];
1498+
value = (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) + buffer[0];
14931499
return true;
14941500

14951501
}
@@ -2277,9 +2283,11 @@ private sealed class TimeoutState
22772283

22782284
private void OnTimeoutAsync(object state)
22792285
{
2280-
#if DEBUG
2281-
Thread.Sleep(13000);
2282-
#endif
2286+
if (_enforceTimeoutDelay)
2287+
{
2288+
Thread.Sleep(_enforcedTimeoutDelayInMilliSeconds);
2289+
}
2290+
22832291
int currentIdentityValue = _timeoutIdentityValue;
22842292
TimeoutState timeoutState = (TimeoutState)state;
22852293
if (timeoutState.IdentityValue == _timeoutIdentityValue)
@@ -2467,7 +2475,7 @@ internal void ReadSni(TaskCompletionSource<object> completion)
24672475
Timeout.Infinite,
24682476
Timeout.Infinite
24692477
);
2470-
2478+
24712479

24722480
// -1 == Infinite
24732481
// 0 == Already timed out (NOTE: To simulate the same behavior as sync we will only timeout on 0 if we receive an IO Pending from SNI)
@@ -3555,11 +3563,10 @@ internal void SendAttention(bool mustTakeWriteLock = false)
35553563
// Set _attentionSending to true before sending attention and reset after setting _attentionSent
35563564
// This prevents a race condition between receiving the attention ACK and setting _attentionSent
35573565
_attentionSending = true;
3558-
35593566
#if DEBUG
35603567
if (!_skipSendAttention)
3561-
{
35623568
#endif
3569+
{
35633570
// Take lock and send attention
35643571
bool releaseLock = false;
35653572
if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose))
@@ -3589,9 +3596,7 @@ internal void SendAttention(bool mustTakeWriteLock = false)
35893596
_parser.Connection._parserLock.Release();
35903597
}
35913598
}
3592-
#if DEBUG
35933599
}
3594-
#endif
35953600

35963601
SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds.
35973602
_attentionSent = true;

src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
<Compile Include="SQL\DataClassificationTest\DataClassificationTest.cs" />
6767
<Compile Include="TracingTests\EventSourceTest.cs" />
6868
<Compile Include="SQL\AdapterTest\AdapterTest.cs" />
69+
<Compile Include="SQL\AsyncTest\AsyncTimeoutTest.cs" />
6970
<Compile Include="SQL\AsyncTest\BeginExecAsyncTest.cs" />
7071
<Compile Include="SQL\AsyncTest\BeginExecReaderAsyncTest.cs" />
7172
<Compile Include="SQL\AsyncTest\XmlReaderAsyncTest.cs" />
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.Data;
9+
using System.Threading.Tasks;
10+
using System.Xml;
11+
using Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals;
12+
using Xunit;
13+
14+
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
15+
{
16+
public static class AsyncTimeoutTest
17+
{
18+
static string delayQuery2s = "WAITFOR DELAY '00:00:02'";
19+
static string delayQuery10s = "WAITFOR DELAY '00:00:10'";
20+
21+
public enum AsyncAPI
22+
{
23+
ExecuteReaderAsync,
24+
ExecuteScalarAsync,
25+
ExecuteXmlReaderAsync
26+
}
27+
28+
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
29+
[ClassData(typeof(AsyncTimeoutTestVariations))]
30+
public static void TestDelayedAsyncTimeout(AsyncAPI api, string commonObj, int delayPeriod, bool marsEnabled) =>
31+
RunTest(api, commonObj, delayPeriod, marsEnabled);
32+
33+
public class AsyncTimeoutTestVariations : IEnumerable<object[]>
34+
{
35+
public IEnumerator<object[]> GetEnumerator()
36+
{
37+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 8000, true };
38+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 5000, true };
39+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 0, true };
40+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 8000, false };
41+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 5000, false };
42+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 0, false };
43+
44+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 8000, true };
45+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 5000, true };
46+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 0, true };
47+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 8000, false };
48+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 5000, false };
49+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 0, false };
50+
51+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 8000, true };
52+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 5000, true };
53+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 0, true };
54+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 8000, false };
55+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 5000, false };
56+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 0, false };
57+
58+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 8000, true };
59+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 5000, true };
60+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 0, true };
61+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 8000, false };
62+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 5000, false };
63+
yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 0, false };
64+
65+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 8000, true };
66+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 5000, true };
67+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 0, true };
68+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 8000, false };
69+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 5000, false };
70+
yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 0, false };
71+
72+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 8000, true };
73+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 5000, true };
74+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 0, true };
75+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 8000, false };
76+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 5000, false };
77+
yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 0, false };
78+
}
79+
80+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
81+
}
82+
83+
private static void RunTest(AsyncAPI api, string commonObj, int timeoutDelay, bool marsEnabled)
84+
{
85+
string connString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
86+
{
87+
MultipleActiveResultSets = marsEnabled
88+
}.ConnectionString;
89+
90+
using (SqlConnection sqlConnection = new SqlConnection(connString))
91+
{
92+
sqlConnection.Open();
93+
if (timeoutDelay != 0)
94+
{
95+
ConnectionHelper.SetEnforcedTimeout(sqlConnection, true, timeoutDelay);
96+
}
97+
switch (commonObj)
98+
{
99+
case "Connection":
100+
QueryAndValidate(api, 1, delayQuery2s, 1, true, true, sqlConnection).Wait();
101+
QueryAndValidate(api, 2, delayQuery2s, 5, false, true, sqlConnection).Wait();
102+
QueryAndValidate(api, 3, delayQuery10s, 1, true, true, sqlConnection).Wait();
103+
QueryAndValidate(api, 4, delayQuery2s, 10, false, true, sqlConnection).Wait();
104+
break;
105+
case "Command":
106+
using (SqlCommand cmd = sqlConnection.CreateCommand())
107+
{
108+
QueryAndValidate(api, 1, delayQuery2s, 1, true, false, sqlConnection, cmd).Wait();
109+
QueryAndValidate(api, 2, delayQuery2s, 5, false, false, sqlConnection, cmd).Wait();
110+
QueryAndValidate(api, 3, delayQuery10s, 1, true, false, sqlConnection, cmd).Wait();
111+
QueryAndValidate(api, 4, delayQuery2s, 10, false, false, sqlConnection, cmd).Wait();
112+
}
113+
break;
114+
}
115+
}
116+
}
117+
118+
private static async Task QueryAndValidate(AsyncAPI api, int index, string delayQuery, int timeout,
119+
bool timeoutExExpected = false, bool useTransaction = false, SqlConnection cn = null, SqlCommand cmd = null)
120+
{
121+
SqlTransaction tx = null;
122+
try
123+
{
124+
if (cn != null)
125+
{
126+
if (cn.State != ConnectionState.Open)
127+
{
128+
await cn.OpenAsync();
129+
}
130+
cmd = cn.CreateCommand();
131+
if (useTransaction)
132+
{
133+
tx = cn.BeginTransaction(IsolationLevel.ReadCommitted);
134+
cmd.Transaction = tx;
135+
}
136+
}
137+
138+
cmd.CommandTimeout = timeout;
139+
if (api != AsyncAPI.ExecuteXmlReaderAsync)
140+
{
141+
cmd.CommandText = delayQuery + $";select {index} as Id;";
142+
}
143+
else
144+
{
145+
cmd.CommandText = delayQuery + $";select {index} as Id FOR XML PATH;";
146+
}
147+
148+
var result = -1;
149+
switch (api)
150+
{
151+
case AsyncAPI.ExecuteReaderAsync:
152+
using (SqlDataReader reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false))
153+
{
154+
while (await reader.ReadAsync().ConfigureAwait(false))
155+
{
156+
var columnIndex = reader.GetOrdinal("Id");
157+
result = reader.GetInt32(columnIndex);
158+
break;
159+
}
160+
}
161+
break;
162+
case AsyncAPI.ExecuteScalarAsync:
163+
result = (int)await cmd.ExecuteScalarAsync().ConfigureAwait(false);
164+
break;
165+
case AsyncAPI.ExecuteXmlReaderAsync:
166+
using (XmlReader reader = await cmd.ExecuteXmlReaderAsync().ConfigureAwait(false))
167+
{
168+
try
169+
{
170+
Assert.True(reader.Settings.Async);
171+
reader.ReadToDescendant("Id");
172+
result = reader.ReadElementContentAsInt();
173+
}
174+
catch (Exception ex)
175+
{
176+
Assert.False(true, "Exception occurred: " + ex.Message);
177+
}
178+
}
179+
break;
180+
}
181+
182+
if (result != index)
183+
{
184+
throw new Exception("High Alert! Wrong data received for index: " + index);
185+
}
186+
else
187+
{
188+
Assert.True(!timeoutExExpected && result == index);
189+
}
190+
}
191+
catch (SqlException e)
192+
{
193+
if (!timeoutExExpected)
194+
throw new Exception("Index " + index + " failed with: " + e.Message);
195+
else
196+
Assert.True(timeoutExExpected && e.Class == 11 && e.Number == -2);
197+
}
198+
finally
199+
{
200+
if (cn != null)
201+
{
202+
if (useTransaction)
203+
tx.Commit();
204+
cn.Close();
205+
}
206+
}
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)