Skip to content

Merge | Port PR 499 to .NET Framework #3255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace Microsoft.Data.SqlClient
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/SqlDataReader/*' />
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
Expand Down Expand Up @@ -97,9 +97,6 @@ internal class SharedState
private SqlSequentialStream _currentStream;
private SqlSequentialTextReader _currentTextReader;

private IsDBNullAsyncCallContext _cachedIsDBNullContext;
private ReadAsyncCallContext _cachedReadAsyncContext;

internal SqlDataReader(SqlCommand command, CommandBehavior behavior)
{
SqlConnection.VerifyExecutePermission();
Expand Down Expand Up @@ -4387,6 +4384,10 @@ internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly/* =
{
// 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 (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
{
sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
}
_snapshot = null;
PrepareAsyncInvocation(useSnapshot: true);
}
Expand Down Expand Up @@ -5318,7 +5319,15 @@ public override Task<bool> ReadAsync(CancellationToken cancellationToken)
return source.Task;
}

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 == default, "cached ReadAsyncCallContext was not properly disposed");

Expand Down Expand Up @@ -5358,6 +5367,10 @@ private static Task<bool> ReadAsyncExecute(Task task, object state)
if (!hasReadRowToken)
{
hasReadRowToken = true;
if (reader.Connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
{
sqlInternalConnection.CachedDataReaderSnapshot = reader._snapshot;
}
reader._snapshot = null;
reader.PrepareAsyncInvocation(useSnapshot: true);
}
Expand All @@ -5377,7 +5390,10 @@ private static Task<bool> 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);
}
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/IsDBNullAsync/*' />
Expand Down Expand Up @@ -5479,7 +5495,15 @@ override public Task<bool> IsDBNullAsync(int i, CancellationToken cancellationTo
registrationHolder.Set(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 == default, "cached ISDBNullAsync context not properly disposed");

Expand Down Expand Up @@ -5517,7 +5541,10 @@ private static Task<bool> 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);
}
}

/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml' path='docs/members[@name="SqlDataReader"]/GetFieldValueAsync/*' />
Expand Down Expand Up @@ -6019,7 +6046,7 @@ private void CompleteAsyncCall<T>(Task<T> task, SqlDataReaderBaseAsyncCallContex
}
}

private sealed class Snapshot
internal sealed class Snapshot
{
public bool _dataReady;
public bool _haltRead;
Expand Down Expand Up @@ -6051,7 +6078,14 @@ private void PrepareAsyncInvocation(bool useSnapshot)

if (_snapshot == null)
{
_snapshot = 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;
Expand Down Expand Up @@ -6124,6 +6158,10 @@ private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj,
stateObj._permitReplayStackTraceToDiffer = false;
#endif

if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
{
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;
}
Expand Down Expand Up @@ -6162,6 +6200,10 @@ private void SwitchToAsyncWithoutSnapshot()
Debug.Assert(_snapshot != null, "Should currently have a snapshot");
Debug.Assert(_stateObj != null && !_stateObj._asyncReadWithoutSnapshot, "Already in async without snapshot");

if (_connection?.InnerConnection is SqlInternalConnection sqlInternalConnection && sqlInternalConnection.CachedDataReaderSnapshot is null)
{
sqlInternalConnection.CachedDataReaderSnapshot = _snapshot;
}
_snapshot = null;
_stateObj.ResetSnapshot();
_stateObj._asyncReadWithoutSnapshot = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ internal abstract class SqlInternalConnection : DbConnectionInternal
internal SqlCommand.ExecuteReaderAsyncCallContext CachedCommandExecuteReaderAsyncContext;
internal SqlCommand.ExecuteNonQueryAsyncCallContext CachedCommandExecuteNonQueryAsyncContext;
internal SqlCommand.ExecuteXmlReaderAsyncCallContext CachedCommandExecuteXmlReaderAsyncContext;

#endif
internal SqlDataReader.Snapshot CachedDataReaderSnapshot;
internal SqlDataReader.IsDBNullAsyncCallContext CachedDataReaderIsDBNullContext;
internal SqlDataReader.ReadAsyncCallContext CachedDataReaderReadAsyncContext;
#endif

// if connection is not open: null
// if connection is open: currently active database
Expand Down
Loading