Skip to content

Commit

Permalink
improve retry condition & configs
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari committed Oct 5, 2020
1 parent 9b344fc commit 5f9cca9
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryingEventArgs.cs" />
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryLogicBase.cs" />
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryLogicBaseProvider.cs" />
<Compile Include="Microsoft\Data\Reliability\SqlConfigurableRetryLogicProviders.cs" />
<Compile Include="Microsoft\Data\Reliability\SqlConfigurableRetryFactory.cs" />
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryIntervalBaseEnumerator.cs" />
<Compile Include="Microsoft\Data\Reliability\SqlRetryIntervalEnumerators.cs" />
<Compile Include="Microsoft\Data\Reliability\Common\SqlRetryLogicProvider.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,35 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Transactions;

namespace Microsoft.Data.SqlClient.Reliability
namespace Microsoft.Data.SqlClient
{
internal class SqlRetryLogic : SqlRetryLogicBase
internal sealed class SqlRetryLogic : SqlRetryLogicBase
{
private const int firstCounter = 1;

public SqlRetryLogic(int numberOfTries, SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate)
public Predicate<string> PreCondition { get; private set; }

public SqlRetryLogic(int numberOfTries,
SqlRetryIntervalBaseEnumerator enumerator,
Predicate<Exception> transientPredicate,
Predicate<string> preCondition)
{
Validate(numberOfTries, enumerator, transientPredicate);

NumberOfTries = numberOfTries;
RetryIntervalEnumerator = enumerator;
TransientPredicate = transientPredicate;
PreCondition = preCondition;
Current = firstCounter;
}

public SqlRetryLogic(int numberOfTries, SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate)
: this(numberOfTries, enumerator, transientPredicate, null)
{
}

public SqlRetryLogic(SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate = null)
: this(firstCounter, enumerator, transientPredicate ?? (_ => false))
{
Expand Down Expand Up @@ -61,5 +73,19 @@ public override bool TryNextInterval(out TimeSpan intervalTime)
}
return result;
}

public override bool RetryCondition(object sender)
{
bool result = true;

if(sender is SqlCommand command)
{
result = Transaction.Current == null // check TransactionScope
&& command.Transaction == null // check SqlTransaction on a SqlCommand
&& PreCondition == null || PreCondition.Invoke(command.CommandText); // if it contains an invalid command to retry
}

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public abstract class SqlRetryLogicBase
/// </summary>
public Predicate<Exception> TransientPredicate { get; protected set; }

/// <summary>
/// Pre-retry validation regarding to the sender state.
/// </summary>
/// <param name="sender">Sender object</param>
/// <returns>True if the sender is authorized to retry the operation</returns>
public virtual bool RetryCondition(object sender) => true;

/// <summary>
/// Try to get the next interval time by the enumerator if the counter does not exceed from the number of retries.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,28 @@ public abstract class SqlRetryLogicBaseProvider
/// Executes a function with a TResult type.
/// </summary>
/// <typeparam name="TResult">The function return type</typeparam>
/// <param name="sender">Sender object</param>
/// <param name="function">The operaiton is likly be in the retry logic if transient condition happens</param>
/// <returns>A TResult object or an exception</returns>
public abstract TResult Execute<TResult>(Func<TResult> function);
public abstract TResult Execute<TResult>(object sender, Func<TResult> function);

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

/// <summary>
/// Execute a function with a generic Task type.
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="function">The operaiton is likly be in the retry logic if transient condition happens</param>
/// <param name="cancellationToken">The cancellation instruction</param>
/// <returns>A Task or an exception</returns>
public abstract Task ExecuteAsync(Func<Task> function, CancellationToken cancellationToken = default);
public abstract Task ExecuteAsync(object sender, Func<Task> function, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,21 @@ namespace Microsoft.Data.SqlClient
/// <summary>
/// Apply a retry logic on an operation.
/// </summary>
public abstract class SqlRetryLogicProvider : SqlRetryLogicBaseProvider
internal class SqlRetryLogicProvider : SqlRetryLogicBaseProvider
{
// safety switch for the preview version
private const string EnableRetryLogicSwitch = "Switch.Microsoft.Data.SqlClient.EnableRetryLogic";
private bool EnableRetryLogic = false;
private readonly bool enableRetryLogic = false;

///
public SqlRetryLogicProvider()
public SqlRetryLogicProvider(SqlRetryLogicBase retryLogic)
{
AppContext.TryGetSwitch(EnableRetryLogicSwitch, out EnableRetryLogic);
}

private void OnRetrying(SqlRetryingEventArgs eventArgs)
{
Retrying?.Invoke(this, eventArgs);
AppContext.TryGetSwitch(EnableRetryLogicSwitch, out enableRetryLogic);
RetryLogic = retryLogic;
}

///
public override TResult Execute<TResult>(Func<TResult> function)
public override TResult Execute<TResult>(object sender, Func<TResult> function)
{
var exceptions = new List<Exception>();
retry:
Expand All @@ -40,12 +36,13 @@ public override TResult Execute<TResult>(Func<TResult> function)
}
catch (Exception e)
{
if (EnableRetryLogic && RetryLogic.TransientPredicate(e))
if (enableRetryLogic && RetryLogic.RetryCondition(sender) && RetryLogic.TransientPredicate(e))
{
exceptions.Add(e);
if (RetryLogic.TryNextInterval(out TimeSpan intervalTime))
{
ApplyRetryEvent(RetryLogic.Current, intervalTime, exceptions);
// The retrying event raises on each retry.
ApplyRetryingEvent(sender, RetryLogic.Current, intervalTime, exceptions);

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

///
public override async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken = default)
public override async Task<TResult> ExecuteAsync<TResult>(object sender, Func<Task<TResult>> function, CancellationToken cancellationToken = default)
{
var exceptions = new List<Exception>();
retry:
Expand All @@ -74,12 +71,13 @@ public override async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> fu
}
catch (Exception e)
{
if (EnableRetryLogic && RetryLogic.TransientPredicate(e))
if (enableRetryLogic && RetryLogic.RetryCondition(sender) && RetryLogic.TransientPredicate(e))
{
exceptions.Add(e);
if (RetryLogic.TryNextInterval(out TimeSpan intervalTime))
{
ApplyRetryEvent(RetryLogic.Current, intervalTime, exceptions);
// The retrying event raises on each retry.
ApplyRetryingEvent(sender, RetryLogic.Current, intervalTime, exceptions);

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

///
public override async Task ExecuteAsync(Func<Task> function, CancellationToken cancellationToken = default)
public override async Task ExecuteAsync(object sender, Func<Task> function, CancellationToken cancellationToken = default)
{
var exceptions = new List<Exception>();
retry:
Expand All @@ -108,12 +106,13 @@ public override async Task ExecuteAsync(Func<Task> function, CancellationToken c
}
catch (Exception e)
{
if (EnableRetryLogic && RetryLogic.TransientPredicate(e))
if (enableRetryLogic && RetryLogic.RetryCondition(sender) && RetryLogic.TransientPredicate(e))
{
exceptions.Add(e);
if (RetryLogic.TryNextInterval(out TimeSpan intervalTime))
{
ApplyRetryEvent(RetryLogic.Current, intervalTime, exceptions);
// The retrying event raises on each retry.
ApplyRetryingEvent(sender, RetryLogic.Current, intervalTime, exceptions);

// TODO: log the retried execution and the throttled exception
await Task.Delay(intervalTime, cancellationToken);
Expand All @@ -130,6 +129,8 @@ public override async Task ExecuteAsync(Func<Task> function, CancellationToken c
}
}
}

#region private methods

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

private void ApplyRetryEvent(int retryCount, TimeSpan intervalTime, List<Exception> exceptions)
private void OnRetrying(object sender, SqlRetryingEventArgs eventArgs) => Retrying?.Invoke(sender, eventArgs);

private void ApplyRetryingEvent(object sender, int retryCount, TimeSpan intervalTime, List<Exception> exceptions)
{
if (Retrying != null)
{
var retryEventArgs = new SqlRetryingEventArgs(retryCount - 1, intervalTime, exceptions);
OnRetrying(retryEventArgs);
OnRetrying(sender, retryEventArgs);
if (retryEventArgs.Cancel)
{
throw CreateException(exceptions, true);
}
}
}
#endregion
}
}
Loading

0 comments on commit 5f9cca9

Please sign in to comment.