Skip to content

Commit 178fe51

Browse files
Wraith2David-Engel
authored andcommitted
perf: rework TdsParserStateObject and SqlDataReader snapshots (#198)
* rework TdsParserStateObject snapshots * cache SqlDataReader snapshots
1 parent a606f11 commit 178fe51

File tree

5 files changed

+353
-201
lines changed

5 files changed

+353
-201
lines changed

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

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ internal class SharedState
8787

8888
private Task _currentTask;
8989
private Snapshot _snapshot;
90+
private Snapshot _cachedSnapshot;
9091
private CancellationTokenSource _cancelAsyncOnCloseTokenSource;
9192
private CancellationToken _cancelAsyncOnCloseToken;
9293

@@ -803,7 +804,7 @@ private bool TryCleanPartialRead()
803804
}
804805

805806
#if DEBUG
806-
if (_stateObj._pendingData)
807+
if (_stateObj.HasPendingData)
807808
{
808809
byte token;
809810
if (!_stateObj.TryPeekByte(out token))
@@ -936,7 +937,7 @@ private bool TryCloseInternal(bool closeReader)
936937

937938
try
938939
{
939-
if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj._pendingData))
940+
if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj.HasPendingData))
940941
{
941942
// It is possible for this to be called during connection close on a
942943
// broken connection, so check state first.
@@ -1118,7 +1119,7 @@ private bool TryConsumeMetaData()
11181119
{
11191120
// warning: Don't check the MetaData property within this function
11201121
// warning: as it will be a reentrant call
1121-
while (_parser != null && _stateObj != null && _stateObj._pendingData && !_metaDataConsumed)
1122+
while (_parser != null && _stateObj != null && _stateObj.HasPendingData && !_metaDataConsumed)
11221123
{
11231124
if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed)
11241125
{
@@ -3053,7 +3054,7 @@ private bool TryHasMoreResults(out bool moreResults)
30533054

30543055
Debug.Assert(null != _command, "unexpected null command from the data reader!");
30553056

3056-
while (_stateObj._pendingData)
3057+
while (_stateObj.HasPendingData)
30573058
{
30583059
byte token;
30593060
if (!_stateObj.TryPeekByte(out token))
@@ -3137,7 +3138,7 @@ private bool TryHasMoreRows(out bool moreRows)
31373138
moreRows = false;
31383139
return true;
31393140
}
3140-
if (_stateObj._pendingData)
3141+
if (_stateObj.HasPendingData)
31413142
{
31423143
// Consume error's, info's, done's on HasMoreRows, so user obtains error on Read.
31433144
byte b;
@@ -3178,7 +3179,7 @@ private bool TryHasMoreRows(out bool moreRows)
31783179
moreRows = false;
31793180
return false;
31803181
}
3181-
if (_stateObj._pendingData)
3182+
if (_stateObj.HasPendingData)
31823183
{
31833184
if (!_stateObj.TryPeekByte(out b))
31843185
{
@@ -3451,7 +3452,7 @@ private bool TryReadInternal(bool setTimeout, out bool more)
34513452
if (moreRows)
34523453
{
34533454
// read the row from the backend (unless it's an altrow were the marker is already inside the altrow ...)
3454-
while (_stateObj._pendingData)
3455+
while (_stateObj.HasPendingData)
34553456
{
34563457
if (_altRowStatus != ALTROWSTATUS.AltRow)
34573458
{
@@ -3483,7 +3484,7 @@ private bool TryReadInternal(bool setTimeout, out bool more)
34833484
}
34843485
}
34853486

3486-
if (!_stateObj._pendingData)
3487+
if (!_stateObj.HasPendingData)
34873488
{
34883489
if (!TryCloseInternal(false /*closeReader*/))
34893490
{
@@ -3507,7 +3508,7 @@ private bool TryReadInternal(bool setTimeout, out bool more)
35073508
{
35083509
// if we are in SingleRow mode, and we've read the first row,
35093510
// read the rest of the rows, if any
3510-
while (_stateObj._pendingData && !_sharedState._dataReady)
3511+
while (_stateObj.HasPendingData && !_sharedState._dataReady)
35113512
{
35123513
if (!_parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady))
35133514
{
@@ -3548,7 +3549,7 @@ private bool TryReadInternal(bool setTimeout, out bool more)
35483549
more = false;
35493550

35503551
#if DEBUG
3551-
if ((!_sharedState._dataReady) && (_stateObj._pendingData))
3552+
if ((!_sharedState._dataReady) && (_stateObj.HasPendingData))
35523553
{
35533554
byte token;
35543555
if (!_stateObj.TryPeekByte(out token))
@@ -3770,6 +3771,10 @@ private bool TryReadColumnInternal(int i, bool readHeaderOnly = false)
37703771
{
37713772
// reset snapshot to save memory use. We can safely do that here because all SqlDataReader values are stable.
37723773
// The retry logic can use the current values to get back to the right state.
3774+
if (_cachedSnapshot is null)
3775+
{
3776+
_cachedSnapshot = _snapshot;
3777+
}
37733778
_snapshot = null;
37743779
PrepareAsyncInvocation(useSnapshot: true);
37753780
}
@@ -4659,6 +4664,10 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
46594664
if (!rowTokenRead)
46604665
{
46614666
rowTokenRead = true;
4667+
if (_cachedSnapshot is null)
4668+
{
4669+
_cachedSnapshot = _snapshot;
4670+
}
46624671
_snapshot = null;
46634672
PrepareAsyncInvocation(useSnapshot: true);
46644673
}
@@ -5112,28 +5121,27 @@ private void PrepareAsyncInvocation(bool useSnapshot)
51125121

51135122
if (_snapshot == null)
51145123
{
5115-
_snapshot = new Snapshot
5116-
{
5117-
_dataReady = _sharedState._dataReady,
5118-
_haltRead = _haltRead,
5119-
_metaDataConsumed = _metaDataConsumed,
5120-
_browseModeInfoConsumed = _browseModeInfoConsumed,
5121-
_hasRows = _hasRows,
5122-
_altRowStatus = _altRowStatus,
5123-
_nextColumnDataToRead = _sharedState._nextColumnDataToRead,
5124-
_nextColumnHeaderToRead = _sharedState._nextColumnHeaderToRead,
5125-
_columnDataBytesRead = _columnDataBytesRead,
5126-
_columnDataBytesRemaining = _sharedState._columnDataBytesRemaining,
5127-
5128-
// _metadata and _altaMetaDataSetCollection must be Cloned
5129-
// before they are updated
5130-
_metadata = _metaData,
5131-
_altMetaDataSetCollection = _altMetaDataSetCollection,
5132-
_tableNames = _tableNames,
5133-
5134-
_currentStream = _currentStream,
5135-
_currentTextReader = _currentTextReader,
5136-
};
5124+
_snapshot = Interlocked.Exchange(ref _cachedSnapshot, null) ?? new Snapshot();
5125+
5126+
_snapshot._dataReady = _sharedState._dataReady;
5127+
_snapshot._haltRead = _haltRead;
5128+
_snapshot._metaDataConsumed = _metaDataConsumed;
5129+
_snapshot._browseModeInfoConsumed = _browseModeInfoConsumed;
5130+
_snapshot._hasRows = _hasRows;
5131+
_snapshot._altRowStatus = _altRowStatus;
5132+
_snapshot._nextColumnDataToRead = _sharedState._nextColumnDataToRead;
5133+
_snapshot._nextColumnHeaderToRead = _sharedState._nextColumnHeaderToRead;
5134+
_snapshot._columnDataBytesRead = _columnDataBytesRead;
5135+
_snapshot._columnDataBytesRemaining = _sharedState._columnDataBytesRemaining;
5136+
5137+
// _metadata and _altaMetaDataSetCollection must be Cloned
5138+
// before they are updated
5139+
_snapshot._metadata = _metaData;
5140+
_snapshot._altMetaDataSetCollection = _altMetaDataSetCollection;
5141+
_snapshot._tableNames = _tableNames;
5142+
5143+
_snapshot._currentStream = _currentStream;
5144+
_snapshot._currentTextReader = _currentTextReader;
51375145

51385146
_stateObj.SetSnapshot();
51395147
}
@@ -5186,6 +5194,10 @@ private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj,
51865194
stateObj._permitReplayStackTraceToDiffer = false;
51875195
#endif
51885196

5197+
if (_cachedSnapshot is null)
5198+
{
5199+
_cachedSnapshot = _snapshot;
5200+
}
51895201
// 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)
51905202
_snapshot = null;
51915203
}
@@ -5224,6 +5236,10 @@ private void SwitchToAsyncWithoutSnapshot()
52245236
Debug.Assert(_snapshot != null, "Should currently have a snapshot");
52255237
Debug.Assert(_stateObj != null && !_stateObj._asyncReadWithoutSnapshot, "Already in async without snapshot");
52265238

5239+
if (_cachedSnapshot is null)
5240+
{
5241+
_cachedSnapshot = _snapshot;
5242+
}
52275243
_snapshot = null;
52285244
_stateObj.ResetSnapshot();
52295245
_stateObj._asyncReadWithoutSnapshot = true;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -697,11 +697,11 @@ internal override void ValidateConnectionForExecute(SqlCommand command)
697697
// or if MARS is off, then a datareader exists
698698
throw ADP.OpenReaderExists(parser.MARSOn);
699699
}
700-
else if (!parser.MARSOn && parser._physicalStateObj._pendingData)
700+
else if (!parser.MARSOn && parser._physicalStateObj.HasPendingData)
701701
{
702702
parser.DrainData(parser._physicalStateObj);
703703
}
704-
Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
704+
Debug.Assert(!parser._physicalStateObj.HasPendingData, "Should not have a busy physicalStateObject at this point!");
705705

706706
parser.RollbackOrphanedAPITransactions();
707707
}
@@ -840,7 +840,7 @@ private void ResetConnection()
840840
// obtains a clone.
841841

842842
Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
843-
Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
843+
Debug.Assert(!_parser._physicalStateObj.HasPendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
844844

845845
if (_fResetConnection)
846846
{

0 commit comments

Comments
 (0)