diff --git a/Directory.Build.props b/Directory.Build.props
index b49fb7c6f..83512e18f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,8 +2,8 @@
- 4.8.4
- 4.6.1.4
+ 4.8.5
+ 4.6.1.54.6.1.0
diff --git a/System.Data.SqlClient.sln b/System.Data.SqlClient.sln
index 42e3df818..6dfcf8dbe 100644
--- a/System.Data.SqlClient.sln
+++ b/System.Data.SqlClient.sln
@@ -90,20 +90,20 @@ Global
{F3E72F35-0351-4D67-9388-725BCAD807BA}.Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Debug|Any CPU
{F3E72F35-0351-4D67-9388-725BCAD807BA}.Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU
{F3E72F35-0351-4D67-9388-725BCAD807BA}.Release|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU
- {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
- {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
+ {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
- {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
- {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
+ {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
- {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
- {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
+ {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
- {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
- {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
+ {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
{45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
diff --git a/src/System/Data/SqlClient/SqlBulkCopy.cs b/src/System/Data/SqlClient/SqlBulkCopy.cs
index ac253e827..864e4412e 100644
--- a/src/System/Data/SqlClient/SqlBulkCopy.cs
+++ b/src/System/Data/SqlClient/SqlBulkCopy.cs
@@ -2598,7 +2598,7 @@ private void CleanUpStateObject(bool isCancelRequested = true)
{
_stateObj.CancelRequest();
}
- _stateObj._internalTimeout = false;
+ _stateObj.SetTimeoutStateStopped();
_stateObj.CloseSession();
_stateObj._bulkCopyOpperationInProgress = false;
_stateObj._bulkCopyWriteTimeout = false;
diff --git a/src/System/Data/SqlClient/SqlDataReader.cs b/src/System/Data/SqlClient/SqlDataReader.cs
index 3e145187a..ac40cbf3a 100644
--- a/src/System/Data/SqlClient/SqlDataReader.cs
+++ b/src/System/Data/SqlClient/SqlDataReader.cs
@@ -892,7 +892,7 @@ private bool TryCloseInternal(bool closeReader)
{
_sharedState._dataReady = true; // set _sharedState._dataReady to not confuse CleanPartialRead
}
- _stateObj._internalTimeout = false;
+ _stateObj.SetTimeoutStateStopped();
if (_sharedState._dataReady)
{
cleanDataFailed = true;
diff --git a/src/System/Data/SqlClient/TdsParser.cs b/src/System/Data/SqlClient/TdsParser.cs
index b98eb83f4..2345bb80f 100644
--- a/src/System/Data/SqlClient/TdsParser.cs
+++ b/src/System/Data/SqlClient/TdsParser.cs
@@ -1590,7 +1590,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead
// If there is data ready, but we didn't exit the loop, then something is wrong
Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?");
- if (stateObj._internalTimeout)
+ if (stateObj.IsTimeoutStateExpired)
{
runBehavior = RunBehavior.Attention;
}
@@ -2157,7 +2157,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead
stateObj._attentionSent = false;
stateObj._attentionReceived = false;
- if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj._internalTimeout)
+ if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj.IsTimeoutStateExpired)
{
// Add attention error to collection - if not RunBehavior.Clean!
stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0));
diff --git a/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System/Data/SqlClient/TdsParserStateObject.cs
index c1d32cd35..9d3fd0970 100644
--- a/src/System/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/System/Data/SqlClient/TdsParserStateObject.cs
@@ -22,6 +22,23 @@ sealed internal class LastIOTimer
internal abstract class TdsParserStateObject
{
+ private sealed class TimeoutState
+ {
+ public const int Stopped = 0;
+ public const int Running = 1;
+ public const int ExpiredAsync = 2;
+ public const int ExpiredSync = 3;
+
+ private readonly int _value;
+
+ public TimeoutState(int value)
+ {
+ _value = value;
+ }
+
+ public int IdentityValue => _value;
+ }
+
private const int AttentionTimeoutSeconds = 5;
// Ticks to consider a connection "good" after a successful I/O (10,000 ticks = 1 ms)
@@ -85,10 +102,18 @@ internal abstract class TdsParserStateObject
// Timeout variables
private long _timeoutMilliseconds;
private long _timeoutTime; // variable used for timeout computations, holds the value of the hi-res performance counter at which this request should expire
+ private int _timeoutState; // expected to be one of the constant values TimeoutStopped, TimeoutRunning, TimeoutExpiredAsync, TimeoutExpiredSync
+ private int _timeoutIdentitySource;
+ private volatile int _timeoutIdentityValue;
internal volatile bool _attentionSent = false; // true if we sent an Attention to the server
internal bool _attentionReceived = false; // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access)
internal volatile bool _attentionSending = false;
- internal bool _internalTimeout = false; // an internal timeout occurred
+
+ // Below 2 properties are used to enforce timeout delays in code to
+ // reproduce issues related to theadpool starvation and timeout delay.
+ // It should always be set to false by default, and only be enabled during testing.
+ internal bool _enforceTimeoutDelay = false;
+ internal int _enforcedTimeoutDelayInMilliSeconds = 5000;
private readonly LastIOTimer _lastSuccessfulIOTimer;
@@ -739,7 +764,7 @@ private void ResetCancelAndProcessAttention()
// operations.
Parser.ProcessPendingAck(this);
}
- _internalTimeout = false;
+ SetTimeoutStateStopped();
}
}
@@ -1017,7 +1042,7 @@ internal bool TryProcessHeader()
return false;
}
- if (_internalTimeout)
+ if (IsTimeoutStateExpired)
{
ThrowExceptionAndWarning();
return true;
@@ -2160,11 +2185,63 @@ internal void OnConnectionClosed()
}
}
- private void OnTimeout(object state)
+ public void SetTimeoutStateStopped()
+ {
+ Interlocked.Exchange(ref _timeoutState, TimeoutState.Stopped);
+ _timeoutIdentityValue = 0;
+ }
+
+ public bool IsTimeoutStateExpired
+ {
+ get
+ {
+ int state = _timeoutState;
+ return state == TimeoutState.ExpiredAsync || state == TimeoutState.ExpiredSync;
+ }
+ }
+
+ private void OnTimeoutAsync(object state)
+ {
+ if (_enforceTimeoutDelay)
+ {
+ Thread.Sleep(_enforcedTimeoutDelayInMilliSeconds);
+ }
+
+ int currentIdentityValue = _timeoutIdentityValue;
+ TimeoutState timeoutState = (TimeoutState)state;
+ if (timeoutState.IdentityValue == _timeoutIdentityValue)
+ {
+ // the return value is not useful here because no choice is going to be made using it
+ // we only want to make this call to set the state knowing that it will be seen later
+ OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredAsync);
+ }
+ else
+ {
+ Debug.WriteLine($"OnTimeoutAsync called with identity state={timeoutState.IdentityValue} but current identity is {currentIdentityValue} so it is being ignored");
+ }
+ }
+
+ private bool OnTimeoutSync(bool asyncClose = false)
+ {
+ return OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredSync, asyncClose);
+ }
+
+ ///
+ /// attempts to change the timout state from the expected state to the target state and if it succeeds
+ /// will setup the the stateobject into the timeout expired state
+ ///
+ /// the state that is the expected current state, state will change only if this is correct
+ /// the state that will be changed to if the expected state is correct
+ /// any close action to be taken by an async task to avoid deadlock.
+ /// boolean value indicating whether the call changed the timeout state
+ private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose = false)
{
- if (!_internalTimeout)
+ Debug.Assert(targetState == TimeoutState.ExpiredAsync || targetState == TimeoutState.ExpiredSync, "OnTimeoutCore must have an expiry state as the targetState");
+
+ bool retval = false;
+ if (Interlocked.CompareExchange(ref _timeoutState, targetState, expectedState) == expectedState)
{
- _internalTimeout = true;
+ retval = true;
// lock protects against Close and Cancel
lock (this)
{
@@ -2190,7 +2267,7 @@ private void OnTimeout(object state)
{
try
{
- SendAttention(mustTakeWriteLock: true);
+ SendAttention(mustTakeWriteLock: true, asyncClose);
}
catch (Exception e)
{
@@ -2262,11 +2339,11 @@ private void OnTimeout(object state)
}
}
}
+ return retval;
}
internal void ReadSni(TaskCompletionSource