Skip to content

Commit

Permalink
Reset cached async state on disposing a command (#1128)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari authored Jul 30, 2021
1 parent 4e72e43 commit ba2dbfe
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
{
return await base.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
catch (System.Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_readAsyncSemaphore.Release();
Expand All @@ -34,6 +39,11 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
{
await base.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
catch (System.Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_writeAsyncSemaphore.Release();
Expand All @@ -50,6 +60,11 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
{
return await base.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
catch (System.Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_readAsyncSemaphore.Release();
Expand All @@ -64,6 +79,11 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
{
await base.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
catch (System.Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_writeAsyncSemaphore.Release();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
{
return await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_readAsyncSemaphore.Release();
Expand All @@ -40,6 +45,11 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
{
await base.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_writeAsyncSemaphore.Release();
Expand All @@ -61,6 +71,11 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
{
return await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_readAsyncSemaphore.Release();
Expand All @@ -80,6 +95,11 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
{
await base.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message);
throw;
}
finally
{
_writeAsyncSemaphore.Release();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ internal bool IsActiveConnectionValid(SqlConnection activeConnection)

internal void ResetAsyncState()
{
SqlClientEventSource.Log.TryTraceEvent("CachedAsyncState.ResetAsyncState | API | ObjectId {0}, Client Connection Id {1}, AsyncCommandInProgress={2}",
_cachedAsyncConnection?.ObjectID, _cachedAsyncConnection?.ClientConnectionId, _cachedAsyncConnection?.AsyncCommandInProgress);
_cachedAsyncCloseCount = -1;
_cachedAsyncResult = null;
if (_cachedAsyncConnection != null)
Expand All @@ -270,6 +272,7 @@ internal void SetActiveConnectionAndResult(TaskCompletionSource<object> completi
{
Debug.Assert(activeConnection != null, "Unexpected null connection argument on SetActiveConnectionAndResult!");
TdsParser parser = activeConnection?.Parser;
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.SetActiveConnectionAndResult | API | ObjectId {0}, Client Connection Id {1}, MARS={2}", activeConnection?.ObjectID, activeConnection?.ClientConnectionId, parser?.MARSOn);
if ((parser == null) || (parser.State == TdsParserState.Closed) || (parser.State == TdsParserState.Broken))
{
throw ADP.ClosedConnectionError();
Expand Down Expand Up @@ -1014,8 +1017,12 @@ protected override DbParameter CreateDbParameter()
protected override void Dispose(bool disposing)
{
if (disposing)
{ // release managed objects
{
// release managed objects
_cachedMetaData = null;

// reset async cache information to allow a second async execute
_cachedAsyncState?.ResetAsyncState();
}
// release unmanaged objects
base.Dispose(disposing);
Expand Down Expand Up @@ -1274,14 +1281,23 @@ private void BeginExecuteNonQueryInternalReadStage(TaskCompletionSource<object>
cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteNonQuery), _activeConnection);
_stateObj.ReadSni(completion);
}
// Cause of a possible unstable runtime situation on facing with `OutOfMemoryException` and `StackOverflowException` exceptions,
// trying to call further functions in the catch of either may fail that should be considered on debuging!
catch (System.OutOfMemoryException e)
{
_activeConnection.Abort(e);
throw;
}
catch (System.StackOverflowException e)
{
_activeConnection.Abort(e);
throw;
}
catch (Exception)
{
// Similarly, if an exception occurs put the stateObj back into the pool.
// and reset async cache information to allow a second async execute
if (null != _cachedAsyncState)
{
_cachedAsyncState.ResetAsyncState();
}
_cachedAsyncState?.ResetAsyncState();
ReliablePutStateObject();
throw;
}
Expand All @@ -1290,7 +1306,9 @@ private void BeginExecuteNonQueryInternalReadStage(TaskCompletionSource<object>
private void VerifyEndExecuteState(Task completionTask, string endMethod, bool fullCheckForColumnEncryption = false)
{
Debug.Assert(completionTask != null);

SqlClientEventSource.Log.TryTraceEvent("SqlCommand.VerifyEndExecuteState | API | ObjectId {0}, Client Connection Id {1}, MARS={2}, AsyncCommandInProgress={3}",
_activeConnection?.ObjectID, _activeConnection?.ClientConnectionId,
_activeConnection?.Parser?.MARSOn, _activeConnection?.AsyncCommandInProgress);
if (completionTask.IsCanceled)
{
if (_stateObj != null)
Expand Down Expand Up @@ -1397,10 +1415,7 @@ public int EndExecuteNonQueryAsync(IAsyncResult asyncResult)
if (asyncException != null)
{
// Leftover exception from the Begin...InternalReadStage
if (cachedAsyncState != null)
{
cachedAsyncState.ResetAsyncState();
}
cachedAsyncState?.ResetAsyncState();
ReliablePutStateObject();
throw asyncException.InnerException;
}
Expand Down Expand Up @@ -1463,6 +1478,9 @@ private int EndExecuteNonQueryInternal(IAsyncResult asyncResult)

private object InternalEndExecuteNonQuery(IAsyncResult asyncResult, bool isInternal, [CallerMemberName] string endMethod = "")
{
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.InternalEndExecuteNonQuery | INFO | ObjectId {0}, Client Connection Id {1}, MARS={2}, AsyncCommandInProgress={3}",
_activeConnection?.ObjectID, _activeConnection?.ClientConnectionId,
_activeConnection?.Parser?.MARSOn, _activeConnection?.AsyncCommandInProgress);
VerifyEndExecuteState((Task)asyncResult, endMethod);
WaitForAsyncResults(asyncResult, isInternal);

Expand Down Expand Up @@ -1547,6 +1565,8 @@ private object InternalEndExecuteNonQuery(IAsyncResult asyncResult, bool isInter

private Task InternalExecuteNonQuery(TaskCompletionSource<object> completion, bool sendToPipe, int timeout, out bool usedCache, bool asyncWrite = false, bool inRetry = false, [CallerMemberName] string methodName = "")
{
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.InternalExecuteNonQuery | INFO | ObjectId {0}, Client Connection Id {1}, AsyncCommandInProgress={2}",
_activeConnection?.ObjectID, _activeConnection?.ClientConnectionId, _activeConnection?.AsyncCommandInProgress);
bool isAsync = (null != completion);
usedCache = false;

Expand Down Expand Up @@ -1783,14 +1803,25 @@ private void BeginExecuteXmlReaderInternalReadStage(TaskCompletionSource<object>
_cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteXmlReader), _activeConnection);
_stateObj.ReadSni(completion);
}
// Cause of a possible unstable runtime situation on facing with `OutOfMemoryException` and `StackOverflowException` exceptions,
// trying to call further functions in the catch of either may fail that should be considered on debuging!
catch (System.OutOfMemoryException e)
{
_activeConnection.Abort(e);
completion.TrySetException(e);
throw;
}
catch (System.StackOverflowException e)
{
_activeConnection.Abort(e);
completion.TrySetException(e);
throw;
}
catch (Exception e)
{
// Similarly, if an exception occurs put the stateObj back into the pool.
// and reset async cache information to allow a second async execute
if (null != _cachedAsyncState)
{
_cachedAsyncState.ResetAsyncState();
}
_cachedAsyncState?.ResetAsyncState();
ReliablePutStateObject();
completion.TrySetException(e);
}
Expand All @@ -1817,6 +1848,7 @@ private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult)
Exception asyncException = ((Task)asyncResult).Exception;
if (asyncException != null)
{
cachedAsyncState?.ResetAsyncState();
ReliablePutStateObject();
throw asyncException.InnerException;
}
Expand Down Expand Up @@ -2014,6 +2046,7 @@ internal SqlDataReader EndExecuteReaderAsync(IAsyncResult asyncResult)
Exception asyncException = ((Task)asyncResult).Exception;
if (asyncException != null)
{
cachedAsyncState?.ResetAsyncState();
ReliablePutStateObject();
throw asyncException.InnerException;
}
Expand All @@ -2037,6 +2070,9 @@ internal SqlDataReader EndExecuteReaderAsync(IAsyncResult asyncResult)

private SqlDataReader EndExecuteReaderInternal(IAsyncResult asyncResult)
{
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.EndExecuteReaderInternal | API | ObjectId {0}, Client Connection Id {1}, MARS={2}, AsyncCommandInProgress={3}",
_activeConnection?.ObjectID, _activeConnection?.ClientConnectionId,
_activeConnection?.Parser?.MARSOn, _activeConnection?.AsyncCommandInProgress);
SqlStatistics statistics = null;
bool success = false;
int? sqlExceptionNumber = null;
Expand Down Expand Up @@ -2407,28 +2443,43 @@ long firstAttemptStart
private void BeginExecuteReaderInternalReadStage(TaskCompletionSource<object> completion)
{
Debug.Assert(completion != null, "CompletionSource should not be null");
SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlCommand.BeginExecuteReaderInternalReadStage | INFO | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}, Command Text '{3}'", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId, CommandText);
// Read SNI does not have catches for async exceptions, handle here.
try
{
// must finish caching information before ReadSni which can activate the callback before returning
cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteReader), _activeConnection);
_stateObj.ReadSni(completion);
}
// Cause of a possible unstable runtime situation on facing with `OutOfMemoryException` and `StackOverflowException` exceptions,
// trying to call further functions in the catch of either may fail that should be considered on debuging!
catch (System.OutOfMemoryException e)
{
_activeConnection.Abort(e);
completion.TrySetException(e);
throw;
}
catch (System.StackOverflowException e)
{
_activeConnection.Abort(e);
completion.TrySetException(e);
throw;
}
catch (Exception e)
{
// Similarly, if an exception occurs put the stateObj back into the pool.
// and reset async cache information to allow a second async execute
if (null != _cachedAsyncState)
{
_cachedAsyncState.ResetAsyncState();
}
_cachedAsyncState?.ResetAsyncState();
ReliablePutStateObject();
completion.TrySetException(e);
}
}

private SqlDataReader InternalEndExecuteReader(IAsyncResult asyncResult, bool isInternal, string endMethod)
{
SqlClientEventSource.Log.TryTraceEvent("SqlCommand.InternalEndExecuteReader | INFO | ObjectId {0}, Client Connection Id {1}, MARS={2}, AsyncCommandInProgress={3}",
_activeConnection?.ObjectID, _activeConnection?.ClientConnectionId,
_activeConnection?.Parser?.MARSOn, _activeConnection?.AsyncCommandInProgress);
VerifyEndExecuteState((Task)asyncResult, endMethod);
WaitForAsyncResults(asyncResult, isInternal);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2872,6 +2872,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
// to the outstanding GCRoot until AppDomain.Unload.
// We live with the above for the time being due to the constraints of the current
// reliability infrastructure provided by the CLR.
SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.ReadAsyncCallback | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error);

TaskCompletionSource<object> source = _networkPacketTaskSource;
#if DEBUG
Expand Down
Loading

0 comments on commit ba2dbfe

Please sign in to comment.