Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8383e19
Extract AsyncHelper from SqlUtil.cs into the utilities namespace
benrr101 Oct 10, 2025
8119c0c
Add generic ContinueTaskWithState - and it's static the whole way thr…
benrr101 Oct 10, 2025
3df7400
Use generic version where not difficult
benrr101 Oct 11, 2025
6500240
Add stateful version of CreateContinuationTask
benrr101 Oct 11, 2025
f4eea9f
Introduce stateless version that chains into the stateful version
benrr101 Oct 11, 2025
8450aaa
Introduce two-generic ContinueTaskWithState
benrr101 Oct 14, 2025
547af51
Introduce two-generic CreateContinuationTaskWithState
benrr101 Oct 14, 2025
847fd25
Replacing usages that didn't show up before?
benrr101 Oct 15, 2025
cb6d1f9
Make non-generic ContinueTaskWithState private, remove exteernal usag…
benrr101 Oct 16, 2025
dd48b59
Rewrite CreateContinuationTask
benrr101 Oct 16, 2025
5b29b7f
Remove seemingly duplicated CreateContinuationTask overload
benrr101 Oct 16, 2025
153b764
Remove the non-generic CreateContinuationTaskWithState overload
benrr101 Oct 16, 2025
3584c99
Rewrite SetTimeoutException
benrr101 Oct 16, 2025
14fed23
SetTimeoutExceptionWithState
benrr101 Oct 16, 2025
7684fe7
Cleanup WaitForCompletion
benrr101 Oct 16, 2025
da6ef3c
Add reference to Moq in unit test project
benrr101 Oct 20, 2025
f8ccc85
Adding tests
benrr101 Oct 20, 2025
45ef372
Removing exception converter from state-less continue task helper
benrr101 Oct 20, 2025
0e3250f
Adding null task cases for CreateContinuationTask
benrr101 Oct 20, 2025
aabd04f
Migrating SqlHelperTest in functional tests to AsyncHelper in unit tests
benrr101 Oct 22, 2025
ba65f50
Address copilot comments, address unobserved exception issue that bre…
benrr101 Oct 22, 2025
453197e
Merge branch 'main' into dev/russellben/asynchelper-generic-state
benrr101 Oct 27, 2025
db584d7
Addressing a couple bits of PR comments
benrr101 Oct 27, 2025
2340c83
Address PR comments
benrr101 Oct 30, 2025
e20f118
Merge branch 'main' into dev/russellben/asynchelper-generic-state
benrr101 Oct 30, 2025
76770ef
Increase timeout and ensure no stack dives on task completion
benrr101 Oct 31, 2025
c7d06b9
*Try*Set everything, add comment as per PR comment
benrr101 Oct 31, 2025
2035cf5
Tweak comments, add logging to unovserved exception observation, incr…
benrr101 Nov 3, 2025
bbc9193
Fix syntax error
benrr101 Nov 4, 2025
3560027
Only trace the event if tracing is enabled (should've read the comments)
benrr101 Nov 4, 2025
8ae7470
Merge branch 'main' into dev/russellben/asynchelper-generic-state
benrr101 Nov 6, 2025
3f5cfa9
Make sure mockOnCancellation is checked on one test, reorder a couple…
benrr101 Nov 6, 2025
10f39c9
Fix tests that don't correspond to their method name
benrr101 Nov 11, 2025
e2a1027
Merge branch 'main' into dev/russellben/asynchelper-generic-state
benrr101 Nov 14, 2025
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
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<PackageVersion Include="Microsoft.SqlServer.Types" Version="160.1000.6" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="System.Data.Odbc" Version="9.0.9" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs">
<Link>Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Utilities\AsyncHelper.cs">
<Link>Microsoft\Data\SqlClient\Utilities\AsyncHelper.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Utilities\ObjectPool.cs">
<Link>Microsoft\Data\SqlClient\Utilities\ObjectPool.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,9 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\TransactionRequest.cs">
<Link>Microsoft\Data\SqlClient\TransactionRequest.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Utilities\AsyncHelper.cs">
<Link>Microsoft\Data\SqlClient\Utilities\AsyncHelper.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Utilities\BufferWriterExtensions.netfx.cs">
<Link>Microsoft\Data\SqlClient\Utilities\BufferWriterExtensions.netfx.cs</Link>
</Compile>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,18 @@ private string GetFormattedMessage(string className, string memberName, string e
#region Trace

#region Traces without if statements

internal void TraceEvent(string message)
{
Trace(message);
}

[NonEvent]
internal void TraceEvent<T0>(string message, T0 args0)
{
Trace(string.Format(message, args0?.ToString() ?? NullStr));
}

[NonEvent]
internal void TraceEvent<T0, T1>(string message, T0 args0, T1 args1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.SqlClient.Utilities;

namespace Microsoft.Data.SqlClient
{
Expand Down Expand Up @@ -250,24 +251,22 @@ private SqlDataReader GetParameterEncryptionDataReader(
bool isRetry)
{
returnTask = AsyncHelper.CreateContinuationTaskWithState(
task: fetchInputParameterEncryptionInfoTask,
taskToContinue: fetchInputParameterEncryptionInfoTask,
state: this,
onSuccess: state =>
onSuccess: this2 =>
{
SqlCommand command = (SqlCommand)state;
bool processFinallyBlockAsync = true;
bool decrementAsyncCountInFinallyBlockAsync = true;

try
{
// Check for any exceptions on network write, before reading.
command.CheckThrowSNIException();
this2.CheckThrowSNIException();

// If it is async, then TryFetchInputParameterEncryptionInfo ->
// RunExecuteReaderTds would have incremented the async count. Decrement it
// when we are about to complete async execute reader.
SqlInternalConnectionTds internalConnectionTds =
command._activeConnection.GetOpenTdsConnection();
SqlInternalConnectionTds internalConnectionTds = this2._activeConnection.GetOpenTdsConnection();
if (internalConnectionTds is not null)
{
internalConnectionTds.DecrementAsyncCount();
Expand All @@ -276,13 +275,13 @@ private SqlDataReader GetParameterEncryptionDataReader(

// Complete executereader.
// @TODO: If we can remove this reference, this could be a static lambda
describeParameterEncryptionDataReader = command.CompleteAsyncExecuteReader(
describeParameterEncryptionDataReader = this2.CompleteAsyncExecuteReader(
isInternal: false,
forDescribeParameterEncryption: true);
Debug.Assert(command._stateObj is null, "non-null state object in PrepareForTransparentEncryption.");
Debug.Assert(this2._stateObj is null, "non-null state object in PrepareForTransparentEncryption.");

// Read the results of describe parameter encryption.
command.ReadDescribeEncryptionParameterResults(
this2.ReadDescribeEncryptionParameterResults(
describeParameterEncryptionDataReader,
describeParameterEncryptionRpcOriginalRpcMap,
isRetry);
Expand All @@ -302,7 +301,7 @@ private SqlDataReader GetParameterEncryptionDataReader(
}
finally
{
command.PrepareTransparentEncryptionFinallyBlock(
this2.PrepareTransparentEncryptionFinallyBlock(
closeDataReader: processFinallyBlockAsync,
decrementAsyncCount: decrementAsyncCountInFinallyBlockAsync,
clearDataStructures: processFinallyBlockAsync,
Expand All @@ -311,10 +310,9 @@ private SqlDataReader GetParameterEncryptionDataReader(
describeParameterEncryptionDataReader: describeParameterEncryptionDataReader);
}
},
onFailure: static (exception, state) =>
onFailure: static (this2, exception) =>
{
SqlCommand command = (SqlCommand)state;
command.CachedAsyncState?.ResetAsyncState();
this2.CachedAsyncState?.ResetAsyncState();

if (exception is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.SqlClient.Utilities;

#if NETFRAMEWORK
using System.Security.Permissions;
Expand Down Expand Up @@ -219,14 +220,12 @@ private IAsyncResult BeginExecuteNonQueryInternal(
if (execNonQuery is not null)
{
AsyncHelper.ContinueTaskWithState(
task: execNonQuery,
completion: localCompletion,
state: Tuple.Create(this, localCompletion),
onSuccess: static state =>
{
var parameters = (Tuple<SqlCommand, TaskCompletionSource<object>>)state;
parameters.Item1.BeginExecuteNonQueryInternalReadStage(parameters.Item2);
});
taskToContinue: execNonQuery,
taskCompletionSource: localCompletion,
state1: this,
state2: localCompletion,
onSuccess: static (this2, localCompletion2) =>
this2.BeginExecuteNonQueryInternalReadStage(localCompletion2));
}
else
{
Expand Down Expand Up @@ -871,8 +870,8 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation(
timeoutCts.Token);

AsyncHelper.ContinueTask(
reconnectTask,
completion,
taskToContinue: reconnectTask,
taskCompletionSource: completion,
onSuccess: () =>
{
if (completion.Task.IsCompleted)
Expand All @@ -896,10 +895,10 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation(
else
{
AsyncHelper.ContinueTaskWithState(
subTask,
completion,
taskToContinue: subTask,
taskCompletionSource: completion,
state: completion,
onSuccess: static state => ((TaskCompletionSource<object>)state).SetResult(null));
onSuccess: static state => state.SetResult(null));
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.SqlClient.Utilities;

#if NETFRAMEWORK
using System.Security.Permissions;
using Microsoft.Data.SqlClient.Utilities;
#endif

namespace Microsoft.Data.SqlClient
Expand Down Expand Up @@ -308,14 +308,12 @@ private IAsyncResult BeginExecuteReaderInternal(
if (writeTask is not null)
{
AsyncHelper.ContinueTaskWithState(
writeTask,
localCompletion,
state: Tuple.Create(this, localCompletion),
onSuccess: static state =>
{
var parameters = (Tuple<SqlCommand, TaskCompletionSource<object>>)state;
parameters.Item1.BeginExecuteReaderInternalReadStage(parameters.Item2);
});
taskToContinue: writeTask,
taskCompletionSource: localCompletion,
state1: this,
state2: localCompletion,
onSuccess: static (this2, localCompletion2) =>
this2.BeginExecuteReaderInternalReadStage(localCompletion2));
}
else
{
Expand Down Expand Up @@ -1605,21 +1603,19 @@ private Task RunExecuteReaderTdsSetupContinuation(
string optionSettings,
Task writeTask)
{
// @TODO: Why use the state version if we can't make this a static helper?
return AsyncHelper.CreateContinuationTaskWithState(
task: writeTask,
state: _activeConnection,
onSuccess: state =>
taskToContinue: writeTask,
state1: this,
state2: Tuple.Create(ds, runBehavior, optionSettings),
onSuccess: static (this2, parameters) =>
{
// This will throw if the connection is closed.
// @TODO: So... can we have something that specifically does that?
((SqlConnection)state).GetOpenTdsConnection();
CachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings);
this2._activeConnection.GetOpenTdsConnection();
this2.CachedAsyncState.SetAsyncReaderState(parameters.Item1, parameters.Item2, parameters.Item3);
},
onFailure: static (exception, state) =>
{
((SqlConnection)state).GetOpenTdsConnection().DecrementAsyncCount();
});
onFailure: static (this2, _, _) =>
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect parameter usage in the onFailure callback. The signature expects Action<TState1, TState2, Exception> but the lambda is using (this2, _, _) which discards both the second state parameter and the exception. The second parameter should be the Tuple state (parameters), not a discard. This should be (this2, parameters, exception) or similar.

Suggested change
onFailure: static (this2, _, _) =>
onFailure: static (this2, parameters, exception) =>

Copilot uses AI. Check for mistakes.
this2._activeConnection.GetOpenTdsConnection().DecrementAsyncCount());
}

// @TODO: This is way too many parameters being shoveled back and forth. We can do better.
Expand All @@ -1640,13 +1636,12 @@ private void RunExecuteReaderTdsSetupReconnectContinuation(
AsyncHelper.SetTimeoutException(
completion,
timeout,
onFailure: static () => SQL.CR_ReconnectTimeout(),
onTimeout: static () => SQL.CR_ReconnectTimeout(),
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name should be onTimeout to match the method signature of SetTimeoutException. Using onFailure will cause a compilation error.

Copilot uses AI. Check for mistakes.
timeoutCts.Token);

// @TODO: With an object to pass around we can use the state-based version
AsyncHelper.ContinueTask(
reconnectTask,
completion,
taskToContinue: reconnectTask,
taskCompletionSource: completion,
onSuccess: () =>
{
if (completion.Task.IsCompleted)
Expand Down Expand Up @@ -1675,10 +1670,10 @@ private void RunExecuteReaderTdsSetupReconnectContinuation(
else
{
AsyncHelper.ContinueTaskWithState(
subTask,
completion,
taskToContinue: subTask,
taskCompletionSource: completion,
state: completion,
onSuccess: static state => ((TaskCompletionSource<object>)state).SetResult(null));
onSuccess: static completion2 => completion2.SetResult(null));
}
});
}
Expand Down Expand Up @@ -1711,14 +1706,13 @@ private SqlDataReader RunExecuteReaderTdsWithTransparentParameterEncryption(
// @TODO: This is a prime candidate for proper async-await execution
TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
AsyncHelper.ContinueTaskWithState(
task: describeParameterEncryptionTask,
completion: completion,
taskToContinue: describeParameterEncryptionTask,
taskCompletionSource: completion,
state: this,
onSuccess: state =>
onSuccess: this2 =>
{
SqlCommand command = (SqlCommand)state;
command.GenerateEnclavePackage();
command.RunExecuteReaderTds(
this2.GenerateEnclavePackage();
this2.RunExecuteReaderTds(
cmdBehavior,
runBehavior,
returnStream,
Expand All @@ -1737,24 +1731,22 @@ private SqlDataReader RunExecuteReaderTdsWithTransparentParameterEncryption(
else
{
AsyncHelper.ContinueTaskWithState(
task: subTask,
completion: completion,
taskToContinue: subTask,
taskCompletionSource: completion,
state: completion,
onSuccess: static state => ((TaskCompletionSource<object>)state).SetResult(null));
onSuccess: static state => state.SetResult(null));
}
},
onFailure: static (exception, state) =>
onFailure: static (this2, exception) =>
{
((SqlCommand)state).CachedAsyncState?.ResetAsyncState();
this2.CachedAsyncState?.ResetAsyncState();
if (exception is not null)
{
// @TODO: This doesn't do anything, afaik.
throw exception;
Comment on lines +1745 to 1746
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO comment indicates that throwing the exception in the onFailure handler may not have the intended effect. The exception is rethrown but might not propagate correctly since this is in a continuation callback. This should either be removed if truly ineffective, or the exception handling should be revised.

Copilot uses AI. Check for mistakes.
}
},
onCancellation: static state =>
{
((SqlCommand)state).CachedAsyncState?.ResetAsyncState();
});
onCancellation: static this2 => this2.CachedAsyncState?.ResetAsyncState());

task = completion.Task;
return ds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Xml;
using Microsoft.Data.Common;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Data.SqlClient.Utilities;

#if NETFRAMEWORK
using System.Security.Permissions;
Expand Down Expand Up @@ -269,14 +270,12 @@ private IAsyncResult BeginExecuteXmlReaderInternal(
if (writeTask is not null)
{
AsyncHelper.ContinueTaskWithState(
task: writeTask,
completion: localCompletion,
state: Tuple.Create(this, localCompletion),
onSuccess: static state =>
{
var parameters = (Tuple<SqlCommand, TaskCompletionSource<object>>)state;
parameters.Item1.BeginExecuteXmlReaderInternalReadStage(parameters.Item2);
});
taskToContinue: writeTask,
taskCompletionSource: localCompletion,
state1: this,
state2: localCompletion,
onSuccess: static (this2, localCompletion2) =>
this2.BeginExecuteXmlReaderInternalReadStage(localCompletion2));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.ConnectionPool;
using Microsoft.Data.SqlClient.Diagnostics;
using Microsoft.SqlServer.Server;
using Microsoft.Data.SqlClient.Utilities;

#if NETFRAMEWORK
using System.Runtime.CompilerServices;
using System.Security.Permissions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Microsoft.Data.ProviderBase;
using Microsoft.Data.SqlClient.Connection;
using Microsoft.Data.SqlClient.ConnectionPool;
using Microsoft.Data.SqlClient.Utilities;
using Microsoft.Identity.Client;

namespace Microsoft.Data.SqlClient
Expand Down
Loading
Loading