Skip to content

Commit fd7c1ed

Browse files
improve retry condition & configs
1 parent 9b344fc commit fd7c1ed

File tree

10 files changed

+319
-170
lines changed

10 files changed

+319
-170
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@
711711
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryingEventArgs.cs" />
712712
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryLogicBase.cs" />
713713
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryLogicBaseProvider.cs" />
714-
<Compile Include="Microsoft\Data\Reliability\SqlConfigurableRetryLogicProviders.cs" />
714+
<Compile Include="Microsoft\Data\Reliability\SqlConfigurableRetryFactory.cs" />
715715
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryIntervalBaseEnumerator.cs" />
716716
<Compile Include="Microsoft\Data\Reliability\SqlRetryIntervalEnumerators.cs" />
717717
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryLogicProvider.cs" />

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Reliability/Common/SqlRetryLogic.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,35 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Transactions;
67

7-
namespace Microsoft.Data.SqlClient.Reliability
8+
namespace Microsoft.Data.SqlClient
89
{
9-
internal class SqlRetryLogic : SqlRetryLogicBase
10+
internal sealed class SqlRetryLogic : SqlRetryLogicBase
1011
{
1112
private const int firstCounter = 1;
1213

13-
public SqlRetryLogic(int numberOfTries, SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate)
14+
public Predicate<string> PreCondition { get; private set; }
15+
16+
public SqlRetryLogic(int numberOfTries,
17+
SqlRetryIntervalBaseEnumerator enumerator,
18+
Predicate<Exception> transientPredicate,
19+
Predicate<string> preCondition)
1420
{
1521
Validate(numberOfTries, enumerator, transientPredicate);
1622

1723
NumberOfTries = numberOfTries;
1824
RetryIntervalEnumerator = enumerator;
1925
TransientPredicate = transientPredicate;
26+
PreCondition = preCondition;
2027
Current = firstCounter;
2128
}
2229

30+
public SqlRetryLogic(int numberOfTries, SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate)
31+
: this(numberOfTries, enumerator, transientPredicate, null)
32+
{
33+
}
34+
2335
public SqlRetryLogic(SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate = null)
2436
: this(firstCounter, enumerator, transientPredicate ?? (_ => false))
2537
{
@@ -61,5 +73,19 @@ public override bool TryNextInterval(out TimeSpan intervalTime)
6173
}
6274
return result;
6375
}
76+
77+
public override bool RetryCondition(object sender)
78+
{
79+
bool result = true;
80+
81+
if(sender is SqlCommand command)
82+
{
83+
result = Transaction.Current == null // check TransactionScope
84+
&& command.Transaction == null // check SqlTransaction on a SqlCommand
85+
&& PreCondition == null || PreCondition.Invoke(command.CommandText); // if it contains an invalid command to retry
86+
}
87+
88+
return result;
89+
}
6490
}
6591
}

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Reliability/Common/SqlRetryLogicBase.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ public abstract class SqlRetryLogicBase
3232
/// </summary>
3333
public Predicate<Exception> TransientPredicate { get; protected set; }
3434

35+
/// <summary>
36+
/// Pre-retry validation regarding to the sender state.
37+
/// </summary>
38+
/// <param name="sender">Sender object</param>
39+
/// <returns>True if the sender is authorized to retry the operation</returns>
40+
public virtual bool RetryCondition(object sender) => true;
41+
3542
/// <summary>
3643
/// Try to get the next interval time by the enumerator if the counter does not exceed from the number of retries.
3744
/// </summary>

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Reliability/Common/SqlRetryLogicBaseProvider.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,28 @@ public abstract class SqlRetryLogicBaseProvider
2727
/// Executes a function with a TResult type.
2828
/// </summary>
2929
/// <typeparam name="TResult">The function return type</typeparam>
30+
/// <param name="sender">Sender object</param>
3031
/// <param name="function">The operaiton is likly be in the retry logic if transient condition happens</param>
3132
/// <returns>A TResult object or an exception</returns>
32-
public abstract TResult Execute<TResult>(Func<TResult> function);
33+
public abstract TResult Execute<TResult>(object sender, Func<TResult> function);
3334

3435
/// <summary>
3536
/// Executes a function with a generic Task and TResult type.
3637
/// </summary>
3738
/// <typeparam name="TResult">Inner function return type</typeparam>
39+
/// <param name="sender">Sender object</param>
3840
/// <param name="function">The operaiton is likly be in the retry logic if transient condition happens</param>
3941
/// <param name="cancellationToken">The cancellation instruction</param>
4042
/// <returns>A task representing TResult or an exception</returns>
41-
public abstract Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken = default);
43+
public abstract Task<TResult> ExecuteAsync<TResult>(object sender, Func<Task<TResult>> function, CancellationToken cancellationToken = default);
4244

4345
/// <summary>
4446
/// Execute a function with a generic Task type.
4547
/// </summary>
48+
/// <param name="sender">Sender object</param>
4649
/// <param name="function">The operaiton is likly be in the retry logic if transient condition happens</param>
4750
/// <param name="cancellationToken">The cancellation instruction</param>
4851
/// <returns>A Task or an exception</returns>
49-
public abstract Task ExecuteAsync(Func<Task> function, CancellationToken cancellationToken = default);
52+
public abstract Task ExecuteAsync(object sender, Func<Task> function, CancellationToken cancellationToken = default);
5053
}
5154
}

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Reliability/Common/SqlRetryLogicProvider.cs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,21 @@ namespace Microsoft.Data.SqlClient
1212
/// <summary>
1313
/// Apply a retry logic on an operation.
1414
/// </summary>
15-
public abstract class SqlRetryLogicProvider : SqlRetryLogicBaseProvider
15+
internal class SqlRetryLogicProvider : SqlRetryLogicBaseProvider
1616
{
1717
// safety switch for the preview version
1818
private const string EnableRetryLogicSwitch = "Switch.Microsoft.Data.SqlClient.EnableRetryLogic";
19-
private bool EnableRetryLogic = false;
19+
private readonly bool enableRetryLogic = false;
2020

2121
///
22-
public SqlRetryLogicProvider()
22+
public SqlRetryLogicProvider(SqlRetryLogicBase retryLogic)
2323
{
24-
AppContext.TryGetSwitch(EnableRetryLogicSwitch, out EnableRetryLogic);
25-
}
26-
27-
private void OnRetrying(SqlRetryingEventArgs eventArgs)
28-
{
29-
Retrying?.Invoke(this, eventArgs);
24+
AppContext.TryGetSwitch(EnableRetryLogicSwitch, out enableRetryLogic);
25+
RetryLogic = retryLogic;
3026
}
3127

3228
///
33-
public override TResult Execute<TResult>(Func<TResult> function)
29+
public override TResult Execute<TResult>(object sender, Func<TResult> function)
3430
{
3531
var exceptions = new List<Exception>();
3632
retry:
@@ -40,12 +36,13 @@ public override TResult Execute<TResult>(Func<TResult> function)
4036
}
4137
catch (Exception e)
4238
{
43-
if (EnableRetryLogic && RetryLogic.TransientPredicate(e))
39+
if (enableRetryLogic && RetryLogic.RetryCondition(sender) && RetryLogic.TransientPredicate(e))
4440
{
4541
exceptions.Add(e);
4642
if (RetryLogic.TryNextInterval(out TimeSpan intervalTime))
4743
{
48-
ApplyRetryEvent(RetryLogic.Current, intervalTime, exceptions);
44+
// The retrying event raises on each retry.
45+
ApplyRetryingEvent(sender, RetryLogic.Current, intervalTime, exceptions);
4946

5047
// TODO: log the retried execution and the throttled exception
5148
Thread.Sleep(intervalTime);
@@ -64,7 +61,7 @@ public override TResult Execute<TResult>(Func<TResult> function)
6461
}
6562

6663
///
67-
public override async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken = default)
64+
public override async Task<TResult> ExecuteAsync<TResult>(object sender, Func<Task<TResult>> function, CancellationToken cancellationToken = default)
6865
{
6966
var exceptions = new List<Exception>();
7067
retry:
@@ -74,12 +71,13 @@ public override async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> fu
7471
}
7572
catch (Exception e)
7673
{
77-
if (EnableRetryLogic && RetryLogic.TransientPredicate(e))
74+
if (enableRetryLogic && RetryLogic.RetryCondition(sender) && RetryLogic.TransientPredicate(e))
7875
{
7976
exceptions.Add(e);
8077
if (RetryLogic.TryNextInterval(out TimeSpan intervalTime))
8178
{
82-
ApplyRetryEvent(RetryLogic.Current, intervalTime, exceptions);
79+
// The retrying event raises on each retry.
80+
ApplyRetryingEvent(sender, RetryLogic.Current, intervalTime, exceptions);
8381

8482
// TODO: log the retried execution and the throttled exception
8583
await Task.Delay(intervalTime, cancellationToken);
@@ -98,7 +96,7 @@ public override async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> fu
9896
}
9997

10098
///
101-
public override async Task ExecuteAsync(Func<Task> function, CancellationToken cancellationToken = default)
99+
public override async Task ExecuteAsync(object sender, Func<Task> function, CancellationToken cancellationToken = default)
102100
{
103101
var exceptions = new List<Exception>();
104102
retry:
@@ -108,12 +106,13 @@ public override async Task ExecuteAsync(Func<Task> function, CancellationToken c
108106
}
109107
catch (Exception e)
110108
{
111-
if (EnableRetryLogic && RetryLogic.TransientPredicate(e))
109+
if (enableRetryLogic && RetryLogic.RetryCondition(sender) && RetryLogic.TransientPredicate(e))
112110
{
113111
exceptions.Add(e);
114112
if (RetryLogic.TryNextInterval(out TimeSpan intervalTime))
115113
{
116-
ApplyRetryEvent(RetryLogic.Current, intervalTime, exceptions);
114+
// The retrying event raises on each retry.
115+
ApplyRetryingEvent(sender, RetryLogic.Current, intervalTime, exceptions);
117116

118117
// TODO: log the retried execution and the throttled exception
119118
await Task.Delay(intervalTime, cancellationToken);
@@ -130,6 +129,8 @@ public override async Task ExecuteAsync(Func<Task> function, CancellationToken c
130129
}
131130
}
132131
}
132+
133+
#region private methods
133134

134135
private Exception CreateException(IList<Exception> exceptions, bool manualCancellation = false)
135136
{
@@ -148,17 +149,20 @@ private Exception CreateException(IList<Exception> exceptions, bool manualCancel
148149
return new AggregateException(message, exceptions);
149150
}
150151

151-
private void ApplyRetryEvent(int retryCount, TimeSpan intervalTime, List<Exception> exceptions)
152+
private void OnRetrying(object sender, SqlRetryingEventArgs eventArgs) => Retrying?.Invoke(sender, eventArgs);
153+
154+
private void ApplyRetryingEvent(object sender, int retryCount, TimeSpan intervalTime, List<Exception> exceptions)
152155
{
153156
if (Retrying != null)
154157
{
155158
var retryEventArgs = new SqlRetryingEventArgs(retryCount - 1, intervalTime, exceptions);
156-
OnRetrying(retryEventArgs);
159+
OnRetrying(sender, retryEventArgs);
157160
if (retryEventArgs.Cancel)
158161
{
159162
throw CreateException(exceptions, true);
160163
}
161164
}
162165
}
166+
#endregion
163167
}
164168
}

0 commit comments

Comments
 (0)