From f221b0a95c064835306a8347d17f9ee808d0c98d Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 12 Feb 2020 19:02:13 +0000 Subject: [PATCH] move datareader caches down to internal connection --- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 69 +++++++++++++------ .../Data/SqlClient/SqlInternalConnection.cs | 10 ++- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 61efb7609f..b996781209 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -27,7 +27,7 @@ namespace Microsoft.Data.SqlClient /// public class SqlDataReader : DbDataReader, IDataReader, IDbColumnSchemaGenerator { - private enum ALTROWSTATUS + internal enum ALTROWSTATUS { Null = 0, // default and after Done AltRow, // after calling NextResult and the first AltRow is available for read @@ -87,7 +87,7 @@ internal class SharedState private Task _currentTask; private Snapshot _snapshot; - private Snapshot _cachedSnapshot; + private CancellationTokenSource _cancelAsyncOnCloseTokenSource; private CancellationToken _cancelAsyncOnCloseToken; @@ -97,8 +97,6 @@ internal class SharedState private SqlSequentialStream _currentStream; private SqlSequentialTextReader _currentTextReader; - private IsDBNullAsyncCallContext _cachedIsDBNullContext; - private ReadAsyncCallContext _cachedReadAsyncContext; internal SqlDataReader(SqlCommand command, CommandBehavior behavior) { @@ -3781,9 +3779,9 @@ private bool TryReadColumnInternal(int i, bool readHeaderOnly = false) { // reset snapshot to save memory use. We can safely do that here because all SqlDataReader values are stable. // The retry logic can use the current values to get back to the right state. - if (_cachedSnapshot is null) + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null) { - _cachedSnapshot = _snapshot; + sqlInternalConnection.CachedDataReaderSnapshot = _snapshot; } _snapshot = null; PrepareAsyncInvocation(useSnapshot: true); @@ -4696,7 +4694,15 @@ public override Task ReadAsync(CancellationToken cancellationToken) registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command); } - var context = Interlocked.Exchange(ref _cachedReadAsyncContext, null) ?? new ReadAsyncCallContext(); + ReadAsyncCallContext context = null; + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + context = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderReadAsyncContext, null); + } + if (context is null) + { + context = new ReadAsyncCallContext(); + } Debug.Assert(context._reader == null && context._source == null && context._disposable == null, "cached ReadAsyncCallContext was not properly disposed"); @@ -4740,9 +4746,9 @@ private static Task ReadAsyncExecute(Task task, object state) if (!hasReadRowToken) { hasReadRowToken = true; - if (reader._cachedSnapshot is null) + if (reader.Connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null) { - reader._cachedSnapshot = reader._snapshot; + sqlInternalConnection.CachedDataReaderSnapshot = reader._snapshot; } reader._snapshot = null; reader.PrepareAsyncInvocation(useSnapshot: true); @@ -4762,7 +4768,10 @@ private static Task ReadAsyncExecute(Task task, object state) private void SetCachedReadAsyncCallContext(ReadAsyncCallContext instance) { - Interlocked.CompareExchange(ref _cachedReadAsyncContext, instance, null); + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + Interlocked.CompareExchange(ref sqlInternalConnection.CachedDataReaderReadAsyncContext, instance, null); + } } /// @@ -4863,7 +4872,15 @@ override public Task IsDBNullAsync(int i, CancellationToken cancellationTo registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command); } - IsDBNullAsyncCallContext context = Interlocked.Exchange(ref _cachedIsDBNullContext, null) ?? new IsDBNullAsyncCallContext(); + IsDBNullAsyncCallContext context = null; + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + context = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderIsDBNullContext, null); + } + if (context is null) + { + context = new IsDBNullAsyncCallContext(); + } Debug.Assert(context._reader == null && context._source == null && context._disposable == null, "cached ISDBNullAsync context not properly disposed"); @@ -4899,7 +4916,10 @@ private static Task IsDBNullAsyncExecute(Task task, object state) private void SetCachedIDBNullAsyncCallContext(IsDBNullAsyncCallContext instance) { - Interlocked.CompareExchange(ref _cachedIsDBNullContext, instance, null); + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + Interlocked.CompareExchange(ref sqlInternalConnection.CachedDataReaderIsDBNullContext, instance, null); + } } /// @@ -5047,7 +5067,7 @@ internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendi #endif - private class Snapshot + internal class Snapshot { public bool _dataReady; public bool _haltRead; @@ -5068,7 +5088,7 @@ private class Snapshot public SqlSequentialTextReader _currentTextReader; } - private abstract class AAsyncCallContext : IDisposable + internal abstract class AAsyncCallContext : IDisposable { internal static readonly Action, object> s_completeCallback = SqlDataReader.CompleteAsyncCallCallback; @@ -5111,7 +5131,7 @@ public virtual void Dispose() } } - private sealed class ReadAsyncCallContext : AAsyncCallContext + internal sealed class ReadAsyncCallContext : AAsyncCallContext { internal static readonly Func> s_execute = SqlDataReader.ReadAsyncExecute; @@ -5132,7 +5152,7 @@ public override void Dispose() } } - private sealed class IsDBNullAsyncCallContext : AAsyncCallContext + internal sealed class IsDBNullAsyncCallContext : AAsyncCallContext { internal static readonly Func> s_execute = SqlDataReader.IsDBNullAsyncExecute; @@ -5381,7 +5401,14 @@ private void PrepareAsyncInvocation(bool useSnapshot) if (_snapshot == null) { - _snapshot = Interlocked.Exchange(ref _cachedSnapshot, null) ?? new Snapshot(); + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + _snapshot = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderSnapshot, null) ?? new Snapshot(); + } + else + { + _snapshot = new Snapshot(); + } _snapshot._dataReady = _sharedState._dataReady; _snapshot._haltRead = _haltRead; @@ -5454,9 +5481,9 @@ private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj, stateObj._permitReplayStackTraceToDiffer = false; #endif - if (_cachedSnapshot is null) + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null) { - _cachedSnapshot = _snapshot; + sqlInternalConnection.CachedDataReaderSnapshot = _snapshot; } // We are setting this to null inside the if-statement because stateObj==null means that the reader hasn't been initialized or has been closed (either way _snapshot should already be null) _snapshot = null; @@ -5496,9 +5523,9 @@ private void SwitchToAsyncWithoutSnapshot() Debug.Assert(_snapshot != null, "Should currently have a snapshot"); Debug.Assert(_stateObj != null && !_stateObj._asyncReadWithoutSnapshot, "Already in async without snapshot"); - if (_cachedSnapshot is null) + if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null) { - _cachedSnapshot = _snapshot; + sqlInternalConnection.CachedDataReaderSnapshot = _snapshot; } _snapshot = null; _stateObj.ResetSnapshot(); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs index e11677b0a1..355b7262e3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs @@ -11,17 +11,21 @@ namespace Microsoft.Data.SqlClient { - abstract internal class SqlInternalConnection : DbConnectionInternal + internal abstract class SqlInternalConnection : DbConnectionInternal { private readonly SqlConnectionString _connectionOptions; private bool _isEnlistedInTransaction; // is the server-side connection enlisted? true while we're enlisted, reset only after we send a null... private byte[] _promotedDTCToken; // token returned by the server when we promote transaction private byte[] _whereAbouts; // cache the whereabouts (DTC Address) for exporting - private bool _isGlobalTransaction = false; // Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction) - private bool _isGlobalTransactionEnabledForServer = false; // Whether Global Transactions are enabled for this Azure SQL DB Server + private bool _isGlobalTransaction; // Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction) + private bool _isGlobalTransactionEnabledForServer; // Whether Global Transactions are enabled for this Azure SQL DB Server private static readonly Guid _globalTransactionTMID = new Guid("1c742caf-6680-40ea-9c26-6b6846079764"); // ID of the Non-MSDTC, Azure SQL DB Transaction Manager + internal SqlDataReader.Snapshot CachedDataReaderSnapshot; + internal SqlDataReader.IsDBNullAsyncCallContext CachedDataReaderIsDBNullContext; + internal SqlDataReader.ReadAsyncCallContext CachedDataReaderReadAsyncContext; + // if connection is not open: null // if connection is open: currently active database internal string CurrentDatabase { get; set; }