Skip to content

Commit 762d24f

Browse files
authored
Perf: Move datareader caches down to internal connection (#499)
1 parent 5b8a602 commit 762d24f

File tree

2 files changed

+55
-24
lines changed

2 files changed

+55
-24
lines changed

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

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace Microsoft.Data.SqlClient
2727
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/SqlDataReader/*' />
2828
public class SqlDataReader : DbDataReader, IDataReader, IDbColumnSchemaGenerator
2929
{
30-
private enum ALTROWSTATUS
30+
internal enum ALTROWSTATUS
3131
{
3232
Null = 0, // default and after Done
3333
AltRow, // after calling NextResult and the first AltRow is available for read
@@ -87,7 +87,7 @@ internal class SharedState
8787

8888
private Task _currentTask;
8989
private Snapshot _snapshot;
90-
private Snapshot _cachedSnapshot;
90+
9191
private CancellationTokenSource _cancelAsyncOnCloseTokenSource;
9292
private CancellationToken _cancelAsyncOnCloseToken;
9393

@@ -97,8 +97,6 @@ internal class SharedState
9797

9898
private SqlSequentialStream _currentStream;
9999
private SqlSequentialTextReader _currentTextReader;
100-
private IsDBNullAsyncCallContext _cachedIsDBNullContext;
101-
private ReadAsyncCallContext _cachedReadAsyncContext;
102100

103101
internal SqlDataReader(SqlCommand command, CommandBehavior behavior)
104102
{
@@ -3781,9 +3779,9 @@ private bool TryReadColumnInternal(int i, bool readHeaderOnly = false)
37813779
{
37823780
// reset snapshot to save memory use. We can safely do that here because all SqlDataReader values are stable.
37833781
// The retry logic can use the current values to get back to the right state.
3784-
if (_cachedSnapshot is null)
3782+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
37853783
{
3786-
_cachedSnapshot = _snapshot;
3784+
sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
37873785
}
37883786
_snapshot = null;
37893787
PrepareAsyncInvocation(useSnapshot: true);
@@ -4705,7 +4703,15 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
47054703
registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command);
47064704
}
47074705

4708-
var context = Interlocked.Exchange(ref _cachedReadAsyncContext, null) ?? new ReadAsyncCallContext();
4706+
ReadAsyncCallContext context = null;
4707+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
4708+
{
4709+
context = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderReadAsyncContext, null);
4710+
}
4711+
if (context is null)
4712+
{
4713+
context = new ReadAsyncCallContext();
4714+
}
47094715

47104716
Debug.Assert(context._reader == null && context._source == null && context._disposable == null, "cached ReadAsyncCallContext was not properly disposed");
47114717

@@ -4749,9 +4755,9 @@ private static Task<bool> ReadAsyncExecute(Task task, object state)
47494755
if (!hasReadRowToken)
47504756
{
47514757
hasReadRowToken = true;
4752-
if (reader._cachedSnapshot is null)
4758+
if (reader.Connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
47534759
{
4754-
reader._cachedSnapshot = reader._snapshot;
4760+
sqlInternalConnection.CachedDataReaderSnapshot = reader._snapshot;
47554761
}
47564762
reader._snapshot = null;
47574763
reader.PrepareAsyncInvocation(useSnapshot: true);
@@ -4771,7 +4777,10 @@ private static Task<bool> ReadAsyncExecute(Task task, object state)
47714777

47724778
private void SetCachedReadAsyncCallContext(ReadAsyncCallContext instance)
47734779
{
4774-
Interlocked.CompareExchange(ref _cachedReadAsyncContext, instance, null);
4780+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
4781+
{
4782+
Interlocked.CompareExchange(ref sqlInternalConnection.CachedDataReaderReadAsyncContext, instance, null);
4783+
}
47754784
}
47764785

47774786
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/IsDBNullAsync/*' />
@@ -4872,7 +4881,15 @@ override public Task<bool> IsDBNullAsync(int i, CancellationToken cancellationTo
48724881
registration = cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command);
48734882
}
48744883

4875-
IsDBNullAsyncCallContext context = Interlocked.Exchange(ref _cachedIsDBNullContext, null) ?? new IsDBNullAsyncCallContext();
4884+
IsDBNullAsyncCallContext context = null;
4885+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
4886+
{
4887+
context = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderIsDBNullContext, null);
4888+
}
4889+
if (context is null)
4890+
{
4891+
context = new IsDBNullAsyncCallContext();
4892+
}
48764893

48774894
Debug.Assert(context._reader == null && context._source == null && context._disposable == null, "cached ISDBNullAsync context not properly disposed");
48784895

@@ -4908,7 +4925,10 @@ private static Task<bool> IsDBNullAsyncExecute(Task task, object state)
49084925

49094926
private void SetCachedIDBNullAsyncCallContext(IsDBNullAsyncCallContext instance)
49104927
{
4911-
Interlocked.CompareExchange(ref _cachedIsDBNullContext, instance, null);
4928+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
4929+
{
4930+
Interlocked.CompareExchange(ref sqlInternalConnection.CachedDataReaderIsDBNullContext, instance, null);
4931+
}
49124932
}
49134933

49144934
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetFieldValueAsync/*' />
@@ -5056,7 +5076,7 @@ internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendi
50565076

50575077
#endif
50585078

5059-
private class Snapshot
5079+
internal class Snapshot
50605080
{
50615081
public bool _dataReady;
50625082
public bool _haltRead;
@@ -5077,7 +5097,7 @@ private class Snapshot
50775097
public SqlSequentialTextReader _currentTextReader;
50785098
}
50795099

5080-
private abstract class AAsyncCallContext<T> : IDisposable
5100+
internal abstract class AAsyncCallContext<T> : IDisposable
50815101
{
50825102
internal static readonly Action<Task<T>, object> s_completeCallback = SqlDataReader.CompleteAsyncCallCallback<T>;
50835103

@@ -5120,7 +5140,7 @@ public virtual void Dispose()
51205140
}
51215141
}
51225142

5123-
private sealed class ReadAsyncCallContext : AAsyncCallContext<bool>
5143+
internal sealed class ReadAsyncCallContext : AAsyncCallContext<bool>
51245144
{
51255145
internal static readonly Func<Task, object, Task<bool>> s_execute = SqlDataReader.ReadAsyncExecute;
51265146

@@ -5141,7 +5161,7 @@ public override void Dispose()
51415161
}
51425162
}
51435163

5144-
private sealed class IsDBNullAsyncCallContext : AAsyncCallContext<bool>
5164+
internal sealed class IsDBNullAsyncCallContext : AAsyncCallContext<bool>
51455165
{
51465166
internal static readonly Func<Task, object, Task<bool>> s_execute = SqlDataReader.IsDBNullAsyncExecute;
51475167

@@ -5390,7 +5410,14 @@ private void PrepareAsyncInvocation(bool useSnapshot)
53905410

53915411
if (_snapshot == null)
53925412
{
5393-
_snapshot = Interlocked.Exchange(ref _cachedSnapshot, null) ?? new Snapshot();
5413+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection)
5414+
{
5415+
_snapshot = Interlocked.Exchange(ref sqlInternalConnection.CachedDataReaderSnapshot, null) ?? new Snapshot();
5416+
}
5417+
else
5418+
{
5419+
_snapshot = new Snapshot();
5420+
}
53945421

53955422
_snapshot._dataReady = _sharedState._dataReady;
53965423
_snapshot._haltRead = _haltRead;
@@ -5463,9 +5490,9 @@ private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj,
54635490
stateObj._permitReplayStackTraceToDiffer = false;
54645491
#endif
54655492

5466-
if (_cachedSnapshot is null)
5493+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
54675494
{
5468-
_cachedSnapshot = _snapshot;
5495+
sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
54695496
}
54705497
// 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)
54715498
_snapshot = null;
@@ -5505,9 +5532,9 @@ private void SwitchToAsyncWithoutSnapshot()
55055532
Debug.Assert(_snapshot != null, "Should currently have a snapshot");
55065533
Debug.Assert(_stateObj != null && !_stateObj._asyncReadWithoutSnapshot, "Already in async without snapshot");
55075534

5508-
if (_cachedSnapshot is null)
5535+
if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
55095536
{
5510-
_cachedSnapshot = _snapshot;
5537+
sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
55115538
}
55125539
_snapshot = null;
55135540
_stateObj.ResetSnapshot();

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@
1111

1212
namespace Microsoft.Data.SqlClient
1313
{
14-
abstract internal class SqlInternalConnection : DbConnectionInternal
14+
internal abstract class SqlInternalConnection : DbConnectionInternal
1515
{
1616
private readonly SqlConnectionString _connectionOptions;
1717
private bool _isEnlistedInTransaction; // is the server-side connection enlisted? true while we're enlisted, reset only after we send a null...
1818
private byte[] _promotedDTCToken; // token returned by the server when we promote transaction
1919
private byte[] _whereAbouts; // cache the whereabouts (DTC Address) for exporting
2020

21-
private bool _isGlobalTransaction = false; // Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
22-
private bool _isGlobalTransactionEnabledForServer = false; // Whether Global Transactions are enabled for this Azure SQL DB Server
21+
private bool _isGlobalTransaction; // Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction)
22+
private bool _isGlobalTransactionEnabledForServer; // Whether Global Transactions are enabled for this Azure SQL DB Server
2323
private static readonly Guid _globalTransactionTMID = new Guid("1c742caf-6680-40ea-9c26-6b6846079764"); // ID of the Non-MSDTC, Azure SQL DB Transaction Manager
2424

25+
internal SqlDataReader.Snapshot CachedDataReaderSnapshot;
26+
internal SqlDataReader.IsDBNullAsyncCallContext CachedDataReaderIsDBNullContext;
27+
internal SqlDataReader.ReadAsyncCallContext CachedDataReaderReadAsyncContext;
28+
2529
// if connection is not open: null
2630
// if connection is open: currently active database
2731
internal string CurrentDatabase { get; set; }

0 commit comments

Comments
 (0)