From dd9cedd3995c2a524ac6b00fceed0b1a28ef62ca Mon Sep 17 00:00:00 2001 From: = Date: Sat, 31 Mar 2018 07:55:23 -0400 Subject: [PATCH 01/20] Extend `onTimeout/async` delegates to take the thrown `OperationCanceledException` as an input parameter (Issue #338) * Added overloads to all sync/async timeout syntax classes to allow passing of exception in onTimeout delegates * Added unit tests --- src/Polly.Shared/Timeout/TimeoutEngine.cs | 4 +- .../Timeout/TimeoutEngineAsync.cs | 4 +- src/Polly.Shared/Timeout/TimeoutSyntax.cs | 155 ++++++- .../Timeout/TimeoutSyntaxAsync.cs | 158 ++++++- .../Timeout/TimeoutTResultSyntax.cs | 156 ++++++- .../Timeout/TimeoutTResultSyntaxAsync.cs | 159 ++++++- .../Timeout/TimeoutAsyncSpecs.cs | 406 ++++++++++++++++- src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs | 353 ++++++++++++++- .../Timeout/TimeoutTResultAsyncSpecs.cs | 416 +++++++++++++++++- .../Timeout/TimeoutTResultSpecs.cs | 355 ++++++++++++++- 10 files changed, 2099 insertions(+), 67 deletions(-) diff --git a/src/Polly.Shared/Timeout/TimeoutEngine.cs b/src/Polly.Shared/Timeout/TimeoutEngine.cs index cf81cae7a8c..2c331523188 100644 --- a/src/Polly.Shared/Timeout/TimeoutEngine.cs +++ b/src/Polly.Shared/Timeout/TimeoutEngine.cs @@ -18,7 +18,7 @@ internal static TResult Implementation( CancellationToken cancellationToken, Func timeoutProvider, TimeoutStrategy timeoutStrategy, - Action onTimeout) + Action onTimeout) { cancellationToken.ThrowIfCancellationRequested(); TimeSpan timeout = timeoutProvider(context); @@ -68,7 +68,7 @@ internal static TResult Implementation( { if (timeoutCancellationTokenSource.IsCancellationRequested) { - onTimeout(context, timeout, actionTask); + onTimeout(context, timeout, actionTask, ex); throw new TimeoutRejectedException("The delegate executed through TimeoutPolicy did not complete within the timeout.", ex); } diff --git a/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs b/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs index 3d5b5614cc4..6f2539f3f30 100644 --- a/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs @@ -12,7 +12,7 @@ internal static async Task ImplementationAsync( Context context, Func timeoutProvider, TimeoutStrategy timeoutStrategy, - Func onTimeoutAsync, + Func onTimeoutAsync, CancellationToken cancellationToken, bool continueOnCapturedContext) { @@ -55,7 +55,7 @@ internal static async Task ImplementationAsync( { if (timeoutCancellationTokenSource.IsCancellationRequested) { - await onTimeoutAsync(context, timeout, actionTask).ConfigureAwait(continueOnCapturedContext); + await onTimeoutAsync(context, timeout, actionTask, e).ConfigureAwait(continueOnCapturedContext); throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", e); } diff --git a/src/Polly.Shared/Timeout/TimeoutSyntax.cs b/src/Polly.Shared/Timeout/TimeoutSyntax.cs index 9fb24bfe3b0..56123ac42dd 100644 --- a/src/Polly.Shared/Timeout/TimeoutSyntax.cs +++ b/src/Polly.Shared/Timeout/TimeoutSyntax.cs @@ -16,7 +16,7 @@ public partial class Policy public static TimeoutPolicy Timeout(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); } @@ -31,7 +31,7 @@ public static TimeoutPolicy Timeout(int seconds) public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); } @@ -52,6 +52,22 @@ public static TimeoutPolicy Timeout(int seconds, Action return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -69,6 +85,23 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -78,7 +111,7 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy public static TimeoutPolicy Timeout(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, TimeoutStrategy.Optimistic, doNothing); } @@ -93,7 +126,7 @@ public static TimeoutPolicy Timeout(TimeSpan timeout) public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, timeoutStrategy, doNothing); } @@ -114,6 +147,22 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, Action timeout, TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(ctx => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -131,6 +180,23 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStr return Timeout(ctx => timeout, timeoutStrategy, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return Timeout(ctx => timeout, timeoutStrategy, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -141,7 +207,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider) { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); } @@ -156,7 +222,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrat { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), timeoutStrategy, doNothing); } @@ -176,6 +242,22 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Action timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -193,6 +275,23 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrat return Timeout(ctx => timeoutProvider(), timeoutStrategy, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(ctx => timeoutProvider(), timeoutStrategy, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -201,7 +300,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrat /// The policy instance. public static TimeoutPolicy Timeout(Func timeoutProvider) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); } @@ -214,7 +313,7 @@ public static TimeoutPolicy Timeout(Func timeoutProvider) /// timeoutProvider public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, timeoutStrategy, doNothing); } @@ -232,6 +331,20 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Act return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -247,6 +360,32 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Tim if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + return new TimeoutPolicy( + (action, context, cancellationToken) => TimeoutEngine.Implementation( + (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + timeoutProvider, + timeoutStrategy, + (ctx, timeout, task, ex) => onTimeout(ctx, timeout, task)) + ); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + return new TimeoutPolicy( (action, context, cancellationToken) => TimeoutEngine.Implementation( (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, diff --git a/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs b/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs index c47be90d93d..6b5a38f46c1 100644 --- a/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs @@ -16,7 +16,7 @@ public partial class Policy public static TimeoutPolicy TimeoutAsync(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -31,7 +31,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds) public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); } @@ -54,6 +54,23 @@ public static TimeoutPolicy TimeoutAsync(int seconds, Func TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -71,6 +88,23 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStr return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// seconds;Value must be greater than zero. + public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -80,7 +114,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStr public static TimeoutPolicy TimeoutAsync(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -95,7 +129,7 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout) public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeout, timeoutStrategy, doNothingAsync); } @@ -116,6 +150,22 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Func timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -133,6 +183,23 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeo return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + + return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -143,7 +210,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider) { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -158,7 +225,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Timeout { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, doNothingAsync); } @@ -178,6 +245,22 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -195,6 +278,23 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Timeout return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -203,7 +303,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Timeout /// The policy instance. public static TimeoutPolicy TimeoutAsync(Func timeoutProvider) { - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -217,7 +317,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider /// timeoutProvider public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Func doNothingAsync = (_, __, ___) => TaskHelper.EmptyTask; + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.EmptyTask; return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); } @@ -236,6 +336,20 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -251,6 +365,34 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + return new TimeoutPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => TimeoutEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + timeoutProvider, + timeoutStrategy, + (ctx, timeout, task, exception) => onTimeoutAsync(context, timeout, task), + cancellationToken, + continueOnCapturedContext) + ); + } + + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy + , Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + return new TimeoutPolicy( (action, context, cancellationToken, continueOnCapturedContext) => TimeoutEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, diff --git a/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs b/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs index 9ef5bf8f464..0c4b98dad27 100644 --- a/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs +++ b/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs @@ -16,7 +16,7 @@ public partial class Policy public static TimeoutPolicy Timeout(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothing); } @@ -32,7 +32,7 @@ public static TimeoutPolicy Timeout(int seconds) public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothing); } @@ -52,6 +52,21 @@ public static TimeoutPolicy Timeout(int seconds, Action(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + return Timeout(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -70,6 +85,24 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrate return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(int seconds, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return Timeout(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -79,7 +112,7 @@ public static TimeoutPolicy Timeout(int seconds, TimeoutStrate public static TimeoutPolicy Timeout(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, TimeoutStrategy.Optimistic, doNothing); } @@ -95,7 +128,7 @@ public static TimeoutPolicy Timeout(TimeSpan timeout) public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeout, timeoutStrategy, doNothing); } @@ -115,6 +148,21 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, Action(ctx => timeout, TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(ctx => timeout, TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -132,6 +180,23 @@ public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutS return Timeout(ctx => timeout, timeoutStrategy, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// The timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeout + public static TimeoutPolicy Timeout(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + TimeoutValidator.ValidateTimeSpanTimeout(timeout); + return Timeout(ctx => timeout, timeoutStrategy, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -142,7 +207,7 @@ public static TimeoutPolicy Timeout(Func timeoutProv { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothing); } @@ -158,7 +223,7 @@ public static TimeoutPolicy Timeout(Func timeoutProv { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(ctx => timeoutProvider(), timeoutStrategy, doNothing); } @@ -178,6 +243,22 @@ public static TimeoutPolicy Timeout(Func timeoutProv return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -196,6 +277,24 @@ public static TimeoutPolicy Timeout(Func timeoutProv return Timeout(ctx => timeoutProvider(), timeoutStrategy, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return Timeout(ctx => timeoutProvider(), timeoutStrategy, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -204,7 +303,7 @@ public static TimeoutPolicy Timeout(Func timeoutProv /// The policy instance. public static TimeoutPolicy Timeout(Func timeoutProvider) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, doNothing); } @@ -218,7 +317,7 @@ public static TimeoutPolicy Timeout(Func ti /// timeoutProvider public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Action doNothing = (_, __, ___) => { }; + Action doNothing = (_, __, ___, ____) => { }; return Timeout(timeoutProvider, timeoutStrategy, doNothing); } @@ -236,6 +335,20 @@ public static TimeoutPolicy Timeout(Func ti return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); } + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, Action onTimeout) + { + return Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + } + /// /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -252,6 +365,33 @@ public static TimeoutPolicy Timeout(Func ti if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + return new TimeoutPolicy( + (action, context, cancellationToken) => TimeoutEngine.Implementation( + action, + context, + cancellationToken, + timeoutProvider, + timeoutStrategy, + (ctx, timeout, task, ex) => onTimeout(ctx, timeout, task)) + ); + } + + /// + /// Builds a that will wait for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The return type of delegates which may be executed through the policy. + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeout + public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); + return new TimeoutPolicy( (action, context, cancellationToken) => TimeoutEngine.Implementation( action, diff --git a/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs b/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs index f2b58a92c92..340ad58be78 100644 --- a/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs @@ -17,7 +17,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds) { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -32,7 +32,7 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutS { TimeoutValidator.ValidateSecondsTimeout(seconds); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, doNothingAsync); } @@ -52,6 +52,22 @@ public static TimeoutPolicy TimeoutAsync(int seconds, Func(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(int seconds, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -69,6 +85,23 @@ public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutS return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The number of seconds after which to timeout. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// seconds;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(int seconds, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); + + return TimeoutAsync(ctx => TimeSpan.FromSeconds(seconds), timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -79,7 +112,7 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout) { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -94,7 +127,7 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Tim { TimeoutValidator.ValidateTimeSpanTimeout(timeout); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeout, timeoutStrategy, doNothingAsync); } @@ -115,6 +148,23 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Fun return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return TimeoutAsync(ctx => timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -132,6 +182,23 @@ public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, Tim return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// The timeout. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The timeout strategy. + /// The policy instance. + /// timeout;Value must be greater than zero. + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(TimeSpan timeout, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(timeout)); + + return TimeoutAsync(ctx => timeout, timeoutStrategy, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -142,7 +209,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeou { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, doNothingAsync); } @@ -157,7 +224,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeou { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, doNothingAsync); } @@ -177,6 +244,22 @@ public static TimeoutPolicy TimeoutAsync(Func timeou return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + + return TimeoutAsync(ctx => timeoutProvider(), TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -191,6 +274,23 @@ public static TimeoutPolicy TimeoutAsync(Func timeou { if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + return TimeoutAsync(ctx => timeoutProvider(), onTimeoutAsync); + } + + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + return TimeoutAsync(ctx => timeoutProvider(), timeoutStrategy, onTimeoutAsync); } @@ -202,7 +302,7 @@ public static TimeoutPolicy TimeoutAsync(Func timeou /// The policy instance. public static TimeoutPolicy TimeoutAsync(Func timeoutProvider) { - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, doNothingAsync); } @@ -215,7 +315,7 @@ public static TimeoutPolicy TimeoutAsync(FuncThe policy instance. public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy) { - Func doNothingAsync = (_, __, ___) => TaskHelper.FromResult(default(TResult)); + Func doNothingAsync = (_, __, ___, ____) => TaskHelper.FromResult(default(TResult)); return TimeoutAsync(timeoutProvider, timeoutStrategy, doNothingAsync); } @@ -233,6 +333,20 @@ public static TimeoutPolicy TimeoutAsync(Func(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); } + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, Func onTimeoutAsync) + { + return TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + } + /// /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. /// @@ -254,10 +368,37 @@ public static TimeoutPolicy TimeoutAsync(Func onTimeoutAsync(ctx, timeout, task), cancellationToken, continueOnCapturedContext) ); } + + /// + /// Builds a that will wait asynchronously for a delegate to complete for a specified period of time. A will be thrown if the delegate does not complete within the configured timeout. + /// + /// A function to provide the timeout for this execution. + /// The timeout strategy. + /// An action to call on timeout, passing the execution context, the timeout applied, the capturing the abandoned, timed-out action, and the captured . + /// The Task parameter will be null if the executed action responded co-operatively to cancellation before the policy timed it out. + /// The policy instance. + /// timeoutProvider + /// onTimeoutAsync + public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) + { + if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); + if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); + + return new TimeoutPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => TimeoutEngine.ImplementationAsync( + action, + context, + timeoutProvider, + timeoutStrategy, + onTimeoutAsync, + cancellationToken, + continueOnCapturedContext) + ); + } } } diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs index 7b6bd23d892..eed53354d5a 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs @@ -120,7 +120,18 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -129,7 +140,18 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.TimeoutAsync(30, null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -147,7 +169,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -705,8 +738,371 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - + #endregion + + #region onTimeout full argument list - pessimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + + })) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__pessimistic() + { + string operationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + + }, contextPassedToExecute)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + })) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + }, context)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + + })) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().NotBeNull(); + } + + [Fact] + public async Task Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. + // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + + Exception exceptionToThrow = new DivideByZeroException(); + + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. + return TaskHelper.EmptyTask; + }; + + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); + throw exceptionToThrow; + })) + .ShouldThrow(); + + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + + } + + #endregion + + #region onTimeout full argument list - optimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_non_null_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() + { + string opeationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(opeationKey); + + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, contextPassedToExecute, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(opeationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, context, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().BeNull(); + } + +#endregion + } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs index e606084faed..b6983fbe35d 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs @@ -107,6 +107,15 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout() policy.ShouldNotThrow(); } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_ontimeout_overload() + { + Action doNothing = (_, __, ___, ____) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, doNothing); + + policy.ShouldNotThrow(); + } + [Fact] public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout() { @@ -116,10 +125,30 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate policy.ShouldNotThrow(); } + [Fact] + public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrategy_and_ontimeout_overload() + { + Action doNothing = (_, __, ___, ____) => { }; + Action policy = () => Policy.Timeout(System.Threading.Timeout.InfiniteTimeSpan, TimeoutStrategy.Optimistic, doNothing); + + policy.ShouldNotThrow(); + } + [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeout"); + } + + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_timespan() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -128,7 +157,18 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.Timeout(30, null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeout"); + } + + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_seconds() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -137,7 +177,7 @@ public void Should_throw_when_onTimeout_is_null_with_seconds() [Fact] public void Should_throw_when_timeoutProvider_is_null() { - Action policy = () => Policy.Timeout((Func) null); + Action policy = () => Policy.Timeout((Func)null); policy.ShouldThrow() .And.ParamName.Should().Be("timeoutProvider"); @@ -146,7 +186,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeout"); + } + + [Fact] + public void Should_throw_when_onTimeout_overload_is_null_with_timeoutprovider() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -302,7 +353,7 @@ public void Should_throw_when_timeout_is_less_than_execution_duration__optimisti policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) // Delegate observes cancellation token, so permitting optimistic cancellation. .ShouldThrow(); } - + [Fact] public void Should_not_throw_when_timeout_is_greater_than_execution_duration__optimistic() { @@ -353,13 +404,14 @@ public void Should_not_be_able_to_cancel_with_unobserved_user_cancellation_token using (CancellationTokenSource userTokenSource = new CancellationTokenSource()) { policy.Invoking(p => p.Execute( - _ => { + _ => + { userTokenSource.Cancel(); // User token cancels in the middle of execution ... - SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), + SystemClock.Sleep(TimeSpan.FromSeconds(timeout * 2), CancellationToken.None // ... but if the executed delegate does not observe it ); - } - , userTokenSource.Token) + } + , userTokenSource.Token) ).ShouldThrow(); // ... it's still the timeout we expect. } } @@ -465,13 +517,13 @@ public void Should_call_ontimeout_with_passed_context__pessimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25* programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) .ShouldThrow(); @@ -484,7 +536,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) { - Func timeoutProvider = ctx => (TimeSpan) ctx["timeout"]; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; @@ -594,7 +646,7 @@ public void Should_call_ontimeout_with_passed_context__optimistic() [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25*programaticallyControlledDelay); + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; @@ -614,7 +666,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu [InlineData(3)] public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) { - Func timeoutProvider = ctx => (TimeSpan) ctx["timeout"]; + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; TimeSpan? timeoutPassedToOnTimeout = null; Action onTimeout = (ctx, span, task) => { timeoutPassedToOnTimeout = span; }; @@ -625,7 +677,7 @@ public void Should_call_ontimeout_with_timeout_supplied_different_for_each_execu // Supply a programatically-controlled timeout, via the execution context. Context context = new Context("SomeOperationKey") { - ["timeout"] = TimeSpan.FromMilliseconds(25*programaticallyControlledDelay) + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; policy.Invoking(p => p.Execute((ctx, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) @@ -649,9 +701,276 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - + + + #endregion + + #region onTimeout with full argument list - pessimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_not_null_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__pessimistic() + { + string operationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute((ctx) => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_full_argument_list_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_with_timeout_full_argument_list_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + + policy.Invoking(p => p.Execute((ctx) => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().NotBeNull(); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. + // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + + Exception exceptionToThrow = new DivideByZeroException(); + + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); + }; + + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + throw exceptionToThrow; + })) + .ShouldThrow(); + + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + + } + + #endregion + + #region onTimeout with full argument list - optimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_not_null_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() + { + string opeationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(opeationKey); + + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute((ctx, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), contextPassedToExecute, userCancellationToken)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(opeationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + + var userCancellationToken = CancellationToken.None; + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; + + policy.Invoking(p => p.Execute((ctx, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().BeNull(); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs index 4eddf7ac13f..ecbffc9cee6 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -120,7 +120,18 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(TimeSpan.FromMinutes(0.5), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -129,7 +140,19 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.TimeoutAsync(30, null); + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_with_full_argument_list_onTimeout() + { + Func onTimeout = null; + Action policy = () => Policy.TimeoutAsync(30, onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -147,7 +170,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), null); + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeoutAsync"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_for_full_argument_list_onTimeout() + { + Func onTimeoutAsync = null; + Action policy = () => Policy.TimeoutAsync(() => TimeSpan.FromSeconds(30), onTimeoutAsync); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeoutAsync"); @@ -707,7 +741,381 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o } + #endregion + + #region onTimeout full argument list - pessimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + /* + This assertion fails on NET40 as the exception is of type TaskCanceledException + Unfortunately, using a #if NET40 switch is not viable as the Polly.Net40Async.Specs + project is using NET45 conditional compilation symbols + */ + // exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_llist_with_passed_context__pessimistic() + { + string operationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, context)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().NotBeNull(); + } + + [Fact] + public async Task Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. + // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + + Exception exceptionToThrow = new DivideByZeroException(); + + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = task.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. + return TaskHelper.EmptyTask; + }; + + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); + throw exceptionToThrow; + })) + .ShouldThrow(); + + await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + } + + #endregion + + #region onTimeout full argument list - optimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_non_null_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + /* + This assertion fails on NET40 as the exception is of type TaskCanceledException + Unfortunately, using a #if NET40 switch is not viable as the Polly.Net40Async.Specs + project is using NET45 conditional compilation symbols + */ + // exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() + { + string operationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + contextPassedToOnTimeout = ctx; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, Exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + timeoutPassedToOnTimeout = span; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; + + policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, context, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + taskPassedToOnTimeout = task; + return TaskHelper.EmptyTask; + }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); + var userCancellationToken = CancellationToken.None; + + policy.Awaiting(async p => await p.ExecuteAsync(async ct => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().BeNull(); + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs index ab1f574a74b..f48c67ba162 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs @@ -120,7 +120,18 @@ public void Should_not_throw_when_timeout_is_infinitetimespan_with_timeoutstrate [Fact] public void Should_throw_when_onTimeout_is_null_with_timespan() { - Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeout"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timespan_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(TimeSpan.FromMinutes(0.5), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -129,7 +140,18 @@ public void Should_throw_when_onTimeout_is_null_with_timespan() [Fact] public void Should_throw_when_onTimeout_is_null_with_seconds() { - Action policy = () => Policy.Timeout(30, null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeout"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_seconds_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(30, onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -147,7 +169,18 @@ public void Should_throw_when_timeoutProvider_is_null() [Fact] public void Should_throw_when_onTimeout_is_null_with_timeoutprovider() { - Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), null); + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); + + policy.ShouldThrow() + .And.ParamName.Should().Be("onTimeout"); + } + + [Fact] + public void Should_throw_when_onTimeout_is_null_with_timeoutprovider_and_onTimeout_is_full_argument_set() + { + Action onTimeout = null; + Action policy = () => Policy.Timeout(() => TimeSpan.FromSeconds(30), onTimeout); policy.ShouldThrow() .And.ParamName.Should().Be("onTimeout"); @@ -736,5 +769,319 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o #endregion + + #region onTimeout full argument list - pessimistic + + [Fact] + public void Should_call_ontimeout_full_argument_llist_with_configured_timeout__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_llist_with_non_null_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__pessimistic() + { + string operationKey = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(operationKey); + + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute((ctx) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; + + policy.Invoking(p => p.Execute((ctx) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + }, context)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().NotBeNull(); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() + { + SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. + // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). + // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. + + Exception exceptionToThrow = new DivideByZeroException(); + + Exception exceptionObservedFromTaskPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => + { + task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); + }; + + TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. + TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; + var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + throw exceptionToThrow; + })) + .ShouldThrow(); + + SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); + exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); + exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); + + } + + #endregion + + #region onTimeout full argument list - optimistic + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_non_null_exception__optimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() + { + string operationKeyy = Guid.NewGuid().ToString(); + Context contextPassedToExecute = new Context(operationKeyy); + + Context contextPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute((ctx, ct) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, contextPassedToExecute, userCancellationToken)) + .ShouldThrow(); + + contextPassedToOnTimeout.Should().NotBeNull(); + contextPassedToOnTimeout.OperationKey.Should().Be(operationKeyy); + contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) + { + Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + + var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) + { + Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; + + TimeSpan? timeoutPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; + var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + // Supply a programatically-controlled timeout, via the execution context. + Context context = new Context("SomeOperationKey") + { + ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) + }; + + policy.Invoking(p => p.Execute((ctx, ct) => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, context, userCancellationToken)) + .ShouldThrow(); + + timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); + } + + [Fact] + public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() + { + Task taskPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; + + TimeSpan timeout = TimeSpan.FromMilliseconds(250); + var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); + var userCancellationToken = CancellationToken.None; + + policy.Invoking(p => p.Execute(ct => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .ShouldThrow(); + + taskPassedToOnTimeout.Should().BeNull(); + } + + #endregion } -} \ No newline at end of file +} From 1fd50c3cd7121310a3b018fe7140e5447e84da71 Mon Sep 17 00:00:00 2001 From: Dusty Hoppe Date: Tue, 29 May 2018 07:36:55 -0400 Subject: [PATCH 02/20] Pull request feedback --- .../Timeout/TimeoutTResultAsyncSpecs.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs index ecbffc9cee6..cc8c8705601 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -791,12 +791,7 @@ public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pe .ShouldThrow(); exceptionPassedToOnTimeout.Should().NotBeNull(); - /* - This assertion fails on NET40 as the exception is of type TaskCanceledException - Unfortunately, using a #if NET40 switch is not viable as the Polly.Net40Async.Specs - project is using NET45 conditional compilation symbols - */ - // exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } [Fact] @@ -991,12 +986,7 @@ public void Should_call_ontimeout_full_argument_list_with_non_null_exception__op .ShouldThrow(); exceptionPassedToOnTimeout.Should().NotBeNull(); - /* - This assertion fails on NET40 as the exception is of type TaskCanceledException - Unfortunately, using a #if NET40 switch is not viable as the Polly.Net40Async.Specs - project is using NET45 conditional compilation symbols - */ - // exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } [Fact] From e4919b6fedcb134eda12a02906c3b1398557358a Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 26 May 2018 17:15:50 +0100 Subject: [PATCH 03/20] Fix issue 455 Better error messaging when the generic execute/async overloads are used on a non-generic cache policy, and the sync/async usage is not aligned between policy and execute call. --- CHANGELOG.md | 3 +++ GitVersionConfig.yaml | 2 +- src/Polly.NetStandard11/Properties/AssemblyInfo.cs | 4 ++-- src/Polly.NetStandard20/Properties/AssemblyInfo.cs | 4 ++-- src/Polly.Shared/Caching/CachePolicy.cs | 2 ++ src/Polly.Shared/Caching/CachePolicyAsync.cs | 2 ++ src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs | 12 ++++++++++++ src/Polly.SharedSpecs/Caching/CacheSpecs.cs | 12 ++++++++++++ .../Caching/CacheTResultAsyncSpecs.cs | 11 +++++++++++ src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs | 12 ++++++++++++ src/Polly.nuspec | 5 +++++ 11 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 728df487dd8..46bd0275a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 6.1.0 +- Improved cache error message (issue 455) + ## 6.0.1 - Version 6 RTM, for integration to ASPNET Core 2.1 IHttpClientFactory diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml index 67f30281d5d..315a5d48dfa 100644 --- a/GitVersionConfig.yaml +++ b/GitVersionConfig.yaml @@ -1 +1 @@ -next-version: 6.0.1 \ No newline at end of file +next-version: 6.1.0 \ No newline at end of file diff --git a/src/Polly.NetStandard11/Properties/AssemblyInfo.cs b/src/Polly.NetStandard11/Properties/AssemblyInfo.cs index 8bea54c3b51..3d2471a1b80 100644 --- a/src/Polly.NetStandard11/Properties/AssemblyInfo.cs +++ b/src/Polly.NetStandard11/Properties/AssemblyInfo.cs @@ -3,8 +3,8 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Polly")] -[assembly: AssemblyInformationalVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyInformationalVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: AssemblyVersion("6.0.0.0")] [assembly: CLSCompliant(true)] diff --git a/src/Polly.NetStandard20/Properties/AssemblyInfo.cs b/src/Polly.NetStandard20/Properties/AssemblyInfo.cs index 7d9cb58df33..7cd1b7d4483 100644 --- a/src/Polly.NetStandard20/Properties/AssemblyInfo.cs +++ b/src/Polly.NetStandard20/Properties/AssemblyInfo.cs @@ -3,8 +3,8 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Polly")] -[assembly: AssemblyInformationalVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyInformationalVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: AssemblyVersion("6.0.0.0")] [assembly: CLSCompliant(true)] diff --git a/src/Polly.Shared/Caching/CachePolicy.cs b/src/Polly.Shared/Caching/CachePolicy.cs index f5438eaf57b..99c3a7e6567 100644 --- a/src/Polly.Shared/Caching/CachePolicy.cs +++ b/src/Polly.Shared/Caching/CachePolicy.cs @@ -55,6 +55,8 @@ internal CachePolicy( [DebuggerStepThrough] internal override TResult ExecuteInternal(Func action, Context context, CancellationToken cancellationToken) { + if (_syncCacheProvider == null) throw new InvalidOperationException("Please use the synchronous-defined policies when calling the synchronous Execute (and similar) methods."); + return CacheEngine.Implementation( _syncCacheProvider.For(), _ttlStrategy.For(), diff --git a/src/Polly.Shared/Caching/CachePolicyAsync.cs b/src/Polly.Shared/Caching/CachePolicyAsync.cs index b9fc3ef4ed2..c5e0b755df0 100644 --- a/src/Polly.Shared/Caching/CachePolicyAsync.cs +++ b/src/Polly.Shared/Caching/CachePolicyAsync.cs @@ -47,6 +47,8 @@ internal CachePolicy( [DebuggerStepThrough] internal override Task ExecuteAsyncInternal(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { + if (_asyncCacheProvider == null) throw new InvalidOperationException("Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods."); + return CacheEngine.ImplementationAsync( _asyncCacheProvider.AsyncFor(), _ttlStrategy.For(), diff --git a/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs index 07a27318512..bdaa49d2419 100644 --- a/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheAsyncSpecs.cs @@ -42,6 +42,18 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_sync_execute_on_an_async_policy() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.CacheAsync(cacheProvider, TimeSpan.FromMinutes(5)); + + Action action = () => cachePolicy.Execute(() => 0); + + action.ShouldThrow(); + } + #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/CacheSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheSpecs.cs index e0f0a986404..83878b0f4ab 100644 --- a/src/Polly.SharedSpecs/Caching/CacheSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Polly.Caching; using Polly.Specs.Helpers; @@ -41,6 +42,17 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_async_execute_on_a_sync_policy() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromMinutes(5)); + + cachePolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))) + .ShouldThrow(); + } + #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs index 733f8fab70b..c50e3cf9fc5 100644 --- a/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheTResultAsyncSpecs.cs @@ -42,6 +42,17 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_sync_execute_on_an_async_policy() + { + IAsyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.CacheAsync(cacheProvider, TimeSpan.FromMinutes(5)); + + Action action = () => cachePolicy.Execute(() => 0); + + action.ShouldThrow(); + } #endregion #region Caching behaviours diff --git a/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs b/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs index 50a71186161..917bb2708c0 100644 --- a/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/CacheTResultSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Polly.Caching; using Polly.Specs.Helpers; @@ -40,6 +41,17 @@ public void Should_throw_when_cache_key_strategy_is_null() action.ShouldThrow().And.ParamName.Should().Be("cacheKeyStrategy"); } + [Fact] + public void Should_throw_informative_exception_when_async_execute_on_a_sync_policy() + { + ISyncCacheProvider cacheProvider = new StubCacheProvider(); + + var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromMinutes(5)); + + cachePolicy.Awaiting(p => p.ExecuteAsync(() => Task.FromResult(0))) + .ShouldThrow(); + } + #endregion #region Caching behaviours diff --git a/src/Polly.nuspec b/src/Polly.nuspec index 263bad60ebb..f43b562d585 100644 --- a/src/Polly.nuspec +++ b/src/Polly.nuspec @@ -13,7 +13,12 @@ Exception Handling Resilience Transient Fault Policy Circuit Breaker CircuitBreaker Retry Wait Cache Cache-aside Bulkhead Fallback Timeout Throttle Parallelization Copyright © 2018, App vNext + 6.1.0 + --------------------- + - Improved cache error message (issue 455) + 6.0.1 + --------------------- - Version 6 RTM, for integration to ASPNET Core 2.1 IHttpClientFactory 6.0.0-v6alpha From 99df35d6a77697eed052cc14ae49c9efd84e17ed Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 26 May 2018 17:16:37 +0100 Subject: [PATCH 04/20] Fix examples in readme Remove references to deprecated overloads in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c53979db9b..77f15a31b5d 100644 --- a/README.md +++ b/README.md @@ -594,7 +594,7 @@ var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffs var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5)); // Execute through the cache as a read-through cache: check the cache first; if not found, execute underlying delegate and store the result in the cache. -TResult result = cachePolicy.Execute(() => getFoo(), new Context("FooKey")); // "FooKey" is the cache key used in this execution. +TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey")); // "FooKey" is the cache key used in this execution. // Define a cache Policy, and catch any cache provider errors for logging. var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), @@ -770,7 +770,7 @@ var policy = Policy .WithPolicyKey("MyDataAccessPolicy"); int id = ... // customer id from somewhere -var customerDetails = policy.Execute(() => GetCustomer(id), +var customerDetails = policy.Execute(context => GetCustomer(id), new Context("GetCustomerDetails", new Dictionary() {{"Type","Customer"},{"Id",id}} )); ``` From 346107af8ea1b51be2f03819ec7afc1010e661b4 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Tue, 29 May 2018 10:37:26 +0100 Subject: [PATCH 05/20] Clarify cache examples in readme --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 77f15a31b5d..9fb70474fe3 100644 --- a/README.md +++ b/README.md @@ -583,19 +583,20 @@ For more detail see: [Bulkhead policy documentation](https://github.com/App-vNex ### Cache ```csharp -// Define a cache Policy in the .NET Framework, using the Polly.Caching.MemoryCache nuget package. +// Example: Define a sync cache Policy in the .NET Framework, using the Polly.Caching.MemoryCache nuget package. var memoryCacheProvider = new Polly.Caching.MemoryCache.MemoryCacheProvider(MemoryCache.Default); var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5)); +// For .NET Core examples see the CacheProviders linked to from https://github.com/App-vNext/Polly/wiki/Cache#working-with-cacheproviders : +// - https://github.com/App-vNext/Polly.Caching.MemoryCache +// - https://github.com/App-vNext/Polly.Caching.IDistributedCache + // Define a cache policy with absolute expiration at midnight tonight. var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1)); // Define a cache policy with sliding expiration: items remain valid for another 5 minutes each time the cache item is used. var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5)); -// Execute through the cache as a read-through cache: check the cache first; if not found, execute underlying delegate and store the result in the cache. -TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey")); // "FooKey" is the cache key used in this execution. - // Define a cache Policy, and catch any cache provider errors for logging. var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), (context, key, ex) => { @@ -603,6 +604,10 @@ var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), } ); +// Execute through the cache as a read-through cache: check the cache first; if not found, execute underlying delegate and store the result in the cache. +// The key to use for caching, for a particular execution, is specified by setting the OperationKey (before v6: ExecutionKey) on a Context instance passed to the execution. Use an overload of the form shown below (or a richer overload including the same elements). +// Example: "FooKey" is the cache key that will be used in the below execution. +TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey")); ``` From 68d9c8cac757c346f27f35491d586537c153250f Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 26 May 2018 19:34:27 +0100 Subject: [PATCH 06/20] Update Readme Add notes on when retry and circuit-breaker rethrow exceptions --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fb70474fe3..11b8403158e 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,9 @@ Policy }); ``` -For more detail see: [Retry policy documentation](https://github.com/App-vNext/Polly/wiki/Retry) on wiki. +If all retries fail, a retry policy rethrows the final exception back to the calling code. + +For more depth see also: [Retry policy documentation on wiki](https://github.com/App-vNext/Polly/wiki/Retry). ### Circuit Breaker @@ -353,7 +355,10 @@ breaker.Isolate(); breaker.Reset(); ``` -For more detail see: [Circuit-Breaker documentation](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker) on wiki. + +Note that circuit-breaker policies [rethrow all exceptions](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#exception-handling), even handled ones. A circuit-breaker exists to measure faults and break the circuit when too many faults occur, but does not orchestrate retries. Combine a circuit-breaker with a retry policy as needed. + +For more depth see also: [Circuit-Breaker documentation on wiki](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker). ### Advanced Circuit Breaker ```csharp From 70b884107ef16cbdfae252a1b4f37c58fec973bb Mon Sep 17 00:00:00 2001 From: Freek van Zee Date: Sat, 23 Jun 2018 14:44:19 +0200 Subject: [PATCH 07/20] Add WithCount RetryPolicySate implementations --- src/Polly.Shared/Polly.Shared.projitems | 4 ++ .../Retry/RetryStateRetryForeverWithCount.cs | 25 +++++++++++++ .../RetryStateRetryForeverWithCountAsync.cs | 25 +++++++++++++ .../RetryStateWaitAndRetryForeverWithCount.cs | 37 +++++++++++++++++++ ...yStateWaitAndRetryForeverWithCountAsync.cs | 36 ++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs create mode 100644 src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs create mode 100644 src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs create mode 100644 src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs diff --git a/src/Polly.Shared/Polly.Shared.projitems b/src/Polly.Shared/Polly.Shared.projitems index 21c11d3ad99..9ec35cfe8b4 100644 --- a/src/Polly.Shared/Polly.Shared.projitems +++ b/src/Polly.Shared/Polly.Shared.projitems @@ -119,6 +119,10 @@ + + + + diff --git a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs new file mode 100644 index 00000000000..79b026d47a6 --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading; + +namespace Polly.Retry +{ + internal partial class RetryStateRetryForeverWithCount : IRetryPolicyState + { + private int _errorCount; + private readonly Action, int, Context> _onRetry; + private readonly Context _context; + + public RetryStateRetryForeverWithCount(Action, int, Context> onRetry, Context context) + { + _onRetry = onRetry; + _context = context; + } + + public bool CanRetry(DelegateResult delegateResult, CancellationToken cancellationToken) + { + _errorCount += 1; + _onRetry(delegateResult, _errorCount, _context); + return true; + } + } +} \ No newline at end of file diff --git a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs new file mode 100644 index 00000000000..283bfc8cf04 --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Polly.Retry +{ + internal partial class RetryStateRetryForeverWithCount : IRetryPolicyState + { + private readonly Func, int, Context, Task> _onRetryAsync; + + public RetryStateRetryForeverWithCount(Func, int, Context, Task> onRetryAsync, Context context) + { + _onRetryAsync = onRetryAsync; + _context = context; + } + + public async Task CanRetryAsync(DelegateResult delegateResult, CancellationToken ct, bool continueOnCapturedContext) + { + _errorCount += 1; + await _onRetryAsync(delegateResult, _errorCount, _context).ConfigureAwait(continueOnCapturedContext); + return true; + } + } +} + diff --git a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs new file mode 100644 index 00000000000..b189ca4814c --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCount.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using Polly.Utilities; + +namespace Polly.Retry +{ + internal partial class RetryStateWaitAndRetryForeverWithCount : IRetryPolicyState + { + private int _errorCount; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; + private readonly Action, int, TimeSpan, Context> _onRetry; + private readonly Context _context; + + public RetryStateWaitAndRetryForeverWithCount(Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry, Context context) + { + _sleepDurationProvider = sleepDurationProvider; + _onRetry = onRetry; + _context = context; + } + + public bool CanRetry(DelegateResult delegateResult, CancellationToken cancellationToken) + { + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + + TimeSpan waitTimeSpan = _sleepDurationProvider(_errorCount, delegateResult, _context); + + _onRetry(delegateResult, _errorCount, waitTimeSpan, _context); + + SystemClock.Sleep(waitTimeSpan, cancellationToken); + + return true; + } + } +} diff --git a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs new file mode 100644 index 00000000000..b56fded5a13 --- /dev/null +++ b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverWithCountAsync.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Polly.Utilities; + +namespace Polly.Retry +{ + internal partial class RetryStateWaitAndRetryForeverWithCount : IRetryPolicyState + { + private readonly Func, int, TimeSpan, Context, Task> _onRetryAsync; + + public RetryStateWaitAndRetryForeverWithCount(Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync, Context context) + { + _sleepDurationProvider = sleepDurationProvider; + _onRetryAsync = onRetryAsync; + _context = context; + } + + public async Task CanRetryAsync(DelegateResult delegateResult, CancellationToken cancellationToken, bool continueOnCapturedContext) + { + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + + TimeSpan waitTimeSpan = _sleepDurationProvider(_errorCount, delegateResult, _context); + + await _onRetryAsync(delegateResult, _errorCount, waitTimeSpan, _context).ConfigureAwait(continueOnCapturedContext); + + await SystemClock.SleepAsync(waitTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext); + + return true; + } + } +} + From e7570a9198b5850de152ad8c758ab55b3ecb74c7 Mon Sep 17 00:00:00 2001 From: Freek van Zee Date: Sat, 23 Jun 2018 15:43:28 +0200 Subject: [PATCH 08/20] Add overloads for RetryForever policies To add the error count to the onRetry actions --- src/Polly.Shared/Retry/RetrySyntax.cs | 108 ++++++++- src/Polly.Shared/Retry/RetrySyntaxAsync.cs | 212 +++++++++++++++++- src/Polly.Shared/Retry/RetryTResultSyntax.cs | 113 +++++++++- .../Retry/RetryTResultSyntaxAsync.cs | 200 ++++++++++++++++- 4 files changed, 621 insertions(+), 12 deletions(-) diff --git a/src/Polly.Shared/Retry/RetrySyntax.cs b/src/Polly.Shared/Retry/RetrySyntax.cs index 2738e997415..cc45f3583da 100644 --- a/src/Polly.Shared/Retry/RetrySyntax.cs +++ b/src/Polly.Shared/Retry/RetrySyntax.cs @@ -128,7 +128,22 @@ public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action< { if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - return policyBuilder.RetryForever((outcome, ctx) => onRetry(outcome)); + return policyBuilder.RetryForever((outcome) => onRetry(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((outcome, i, ctx) => onRetry(outcome, i)); } /// @@ -153,6 +168,28 @@ public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action< ), policyBuilder.ExceptionPredicates); } + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy((action, context, cancellationToken) => RetryEngine.Implementation( + (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateRetryForeverWithCount((outcome, i, ctx) => onRetry(outcome.Exception, i, ctx), context) + ), policyBuilder.ExceptionPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -510,6 +547,27 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => onRetry(exception, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the raised exception and @@ -530,6 +588,26 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the raised exception and @@ -557,5 +635,33 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, (outcome, timespan, ctx) => onRetry(outcome.Exception, timespan, ctx), context) ), policyBuilder.ExceptionPredicates); } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy((action, context, cancellationToken) => RetryEngine.Implementation( + (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount( + (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), + (outcome, i, timespan, ctx) => onRetry(outcome.Exception, i, timespan, ctx), context) + ), policyBuilder.ExceptionPredicates); + } } } \ No newline at end of file diff --git a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs index 21f9459ee1d..60693163e70 100644 --- a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs @@ -169,7 +169,7 @@ public static RetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retry (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -204,7 +204,26 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Ac return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome) + onRetryAsync: async (outcome) => onRetry(outcome) +#pragma warning restore 1998 + ); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( +#pragma warning disable 1998 // async method has no awaits, will run synchronously + onRetryAsync: async (outcome, i) => onRetry(outcome, i) #pragma warning restore 1998 ); } @@ -221,7 +240,22 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, ctx) => onRetryAsync(outcome)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome) => onRetryAsync(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i) => onRetryAsync(outcome, i)); } /// @@ -243,6 +277,25 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Ac ); } + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( +#pragma warning disable 1998 // async method has no awaits, will run synchronously + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will retry indefinitely /// calling on each retry with the raised exception and context data. @@ -259,7 +312,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -268,6 +321,31 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu ), policyBuilder.ExceptionPredicates); } + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the raised exception, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateRetryForeverWithCount((outcome, i, ctx) => onRetryAsync(outcome.Exception, i, ctx), context), + continueOnCapturedContext + ), policyBuilder.ExceptionPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -465,7 +543,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, in (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -805,7 +883,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, IE (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, @@ -868,6 +946,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => onRetry(exception, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception. @@ -889,6 +988,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception and retry count. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => onRetryAsync(exception, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception and @@ -913,6 +1033,30 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, +#pragma warning disable 1998 // async method has no awaits, will run synchronously + async (exception, i, timespan, ctx) => onRetry(exception, i, timespan, ctx) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception and @@ -933,6 +1077,26 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil ); } + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + /// /// Builds a that will wait and retry indefinitely /// calling on each retry with the raised exception and @@ -953,13 +1117,45 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil (action, context, cancellationToken, continueOnCapturedContext) => RetryEngine.ImplementationAsync( async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, () => new RetryStateWaitAndRetryForever( (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), - (outcome, timespan, ctx) => onRetryAsync(outcome.Exception, timespan, ctx), + (outcome, timespan, ctx) => onRetryAsync(outcome.Exception, timespan, ctx), + context), + continueOnCapturedContext + ), policyBuilder.ExceptionPredicates); + } + + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + PredicateHelper.EmptyResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount( + (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), + (outcome, i, timespan, ctx) => onRetryAsync(outcome.Exception, i, timespan, ctx), context), continueOnCapturedContext ), policyBuilder.ExceptionPredicates); diff --git a/src/Polly.Shared/Retry/RetryTResultSyntax.cs b/src/Polly.Shared/Retry/RetryTResultSyntax.cs index 1ade32df69f..6fdaaef261a 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntax.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntax.cs @@ -129,7 +129,22 @@ public static RetryPolicy RetryForever(this PolicyBuilder onRetry(outcome)); + return policyBuilder.RetryForever((outcome) => onRetry(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForever((outcome, i) => onRetry(outcome, i)); } /// @@ -157,6 +172,31 @@ public static RetryPolicy RetryForever(this PolicyBuilder + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + (action, context, cancellationToken) => RetryEngine.Implementation( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateRetryForeverWithCount(onRetry, context) + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -560,6 +600,27 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForever( + (retryCount, context) => sleepDurationProvider(retryCount), + (exception, i, timespan, context) => onRetry(exception, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -580,6 +641,26 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -609,5 +690,35 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild policyBuilder.ResultPredicates ); } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return new RetryPolicy( + (action, context, cancellationToken) => RetryEngine.Implementation( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount(sleepDurationProvider, onRetry, context) + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates + ); + } } } \ No newline at end of file diff --git a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs index 8b826288dfe..24a87eb7a62 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs @@ -205,7 +205,26 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, ctx) => onRetry(outcome) + onRetryAsync: async (outcome) => onRetry(outcome) +#pragma warning restore 1998 + ); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( +#pragma warning disable 1998 // async method has no awaits, will run synchronously + onRetryAsync: async (outcome, i) => onRetry(outcome, i) #pragma warning restore 1998 ); } @@ -222,7 +241,22 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, ctx) => onRetryAsync(outcome)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome) => onRetryAsync(outcome)); + } + + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i) => onRetryAsync(outcome, i)); } /// @@ -244,6 +278,25 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder ); } + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call on each retry. + /// The policy instance. + /// onRetry + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Action, int, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.RetryForeverAsync( +#pragma warning disable 1998 // async method has no awaits, will run synchronously + onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will retry indefinitely /// calling on each retry with the handled exception or result and context data. @@ -271,6 +324,33 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder.ResultPredicates); } + /// + /// Builds a that will retry indefinitely + /// calling on each retry with the handled exception or result, retry count and context data. + /// + /// The policy builder. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// onRetryAsync + public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Func, int, Context, Task> onRetryAsync) + { + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateRetryForeverWithCount(onRetryAsync, context), + continueOnCapturedContext + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -869,6 +949,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, context) => sleepDurationProvider(retryCount), + (outcome, i, timespan, context) => onRetry(outcome, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result. @@ -890,6 +991,27 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and retry count. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return policyBuilder.WaitAndRetryForeverAsync( + (retryCount, context) => sleepDurationProvider(retryCount), + (outcome, i, timespan, context) => onRetryAsync(outcome, i, timespan) + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -913,6 +1035,29 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, int, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetryForeverAsync( + sleepDurationProvider, +#pragma warning disable 1998 // async method has no awaits, will run synchronously + async (outcome, i, timespan, ctx) => onRetry(outcome, i, timespan, ctx) +#pragma warning restore 1998 + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -933,6 +1078,26 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy ); } + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + /// /// Builds a that will wait and retry indefinitely until the action succeeds, /// calling on each retry with the handled exception or result and @@ -963,6 +1128,37 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy policyBuilder.ExceptionPredicates, policyBuilder.ResultPredicates); } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result, retry count and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, int, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); + + return new RetryPolicy( + (action, context, cancellationToken, continueOnCapturedContext) => + RetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateWaitAndRetryForeverWithCount(sleepDurationProvider, onRetryAsync, context), + continueOnCapturedContext + ), + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates); + } } } From 8c4e74fe013bc4f492e129945d7b1114bc830e5b Mon Sep 17 00:00:00 2001 From: Freek van Zee Date: Sun, 24 Jun 2018 11:20:36 +0200 Subject: [PATCH 09/20] Fix endless loop and solve ambiguity on RetrySyntax classes --- src/Polly.Shared/Retry/RetrySyntax.cs | 3 ++- src/Polly.Shared/Retry/RetrySyntaxAsync.cs | 4 ++-- src/Polly.Shared/Retry/RetryTResultSyntax.cs | 2 +- src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Polly.Shared/Retry/RetrySyntax.cs b/src/Polly.Shared/Retry/RetrySyntax.cs index cc45f3583da..c1dd6b8ce8b 100644 --- a/src/Polly.Shared/Retry/RetrySyntax.cs +++ b/src/Polly.Shared/Retry/RetrySyntax.cs @@ -128,7 +128,8 @@ public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action< { if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - return policyBuilder.RetryForever((outcome) => onRetry(outcome)); + //return policyBuilder.RetryForever((outcome, ctx) => onRetry(outcome)); + return policyBuilder.RetryForever((Exception outcome, Context ctx) => onRetry(outcome)); } /// diff --git a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs index 60693163e70..7b36f6ff2e4 100644 --- a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs @@ -204,7 +204,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Ac return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome) => onRetry(outcome) + onRetryAsync: async (Exception outcome, Context ctx) => onRetry(outcome) #pragma warning restore 1998 ); } @@ -240,7 +240,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome) => onRetryAsync(outcome)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (Exception outcome, Context ctx) => onRetryAsync(outcome)); } /// diff --git a/src/Polly.Shared/Retry/RetryTResultSyntax.cs b/src/Polly.Shared/Retry/RetryTResultSyntax.cs index 6fdaaef261a..bb9943a20c3 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntax.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntax.cs @@ -129,7 +129,7 @@ public static RetryPolicy RetryForever(this PolicyBuilder onRetry(outcome)); + return policyBuilder.RetryForever((DelegateResult outcome, Context ctx) => onRetry(outcome)); } /// diff --git a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs index 24a87eb7a62..5619b0f339d 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs @@ -205,7 +205,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome) => onRetry(outcome) + onRetryAsync: async (DelegateResult outcome, Context ctx) => onRetry(outcome) #pragma warning restore 1998 ); } @@ -241,7 +241,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome) => onRetryAsync(outcome)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (DelegateResult outcome, Context ctx) => onRetryAsync(outcome)); } /// From 56cf3da3bcbfc706897d89e8763126fc348009b1 Mon Sep 17 00:00:00 2001 From: Freek van Zee Date: Sun, 24 Jun 2018 11:25:48 +0200 Subject: [PATCH 10/20] Remove possibility of Arithmetic Overflow on RetryStateRetryForeverWithCount --- src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs | 6 +++++- .../Retry/RetryStateRetryForeverWithCountAsync.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs index 79b026d47a6..b04137b9b79 100644 --- a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs +++ b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCount.cs @@ -17,7 +17,11 @@ public RetryStateRetryForeverWithCount(Action, int, Cont public bool CanRetry(DelegateResult delegateResult, CancellationToken cancellationToken) { - _errorCount += 1; + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + _onRetry(delegateResult, _errorCount, _context); return true; } diff --git a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs index 283bfc8cf04..793a9f753ca 100644 --- a/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs +++ b/src/Polly.Shared/Retry/RetryStateRetryForeverWithCountAsync.cs @@ -16,7 +16,11 @@ public RetryStateRetryForeverWithCount(Func, int, Contex public async Task CanRetryAsync(DelegateResult delegateResult, CancellationToken ct, bool continueOnCapturedContext) { - _errorCount += 1; + if (_errorCount < int.MaxValue) + { + _errorCount += 1; + } + await _onRetryAsync(delegateResult, _errorCount, _context).ConfigureAwait(continueOnCapturedContext); return true; } From 07ca54be4fb75c7c2e49bd2124b8b594d828169c Mon Sep 17 00:00:00 2001 From: Freek van Zee Date: Sun, 24 Jun 2018 11:33:30 +0200 Subject: [PATCH 11/20] Remove commented line --- src/Polly.Shared/Retry/RetrySyntax.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Polly.Shared/Retry/RetrySyntax.cs b/src/Polly.Shared/Retry/RetrySyntax.cs index c1dd6b8ce8b..82588e21bf4 100644 --- a/src/Polly.Shared/Retry/RetrySyntax.cs +++ b/src/Polly.Shared/Retry/RetrySyntax.cs @@ -128,7 +128,6 @@ public static RetryPolicy RetryForever(this PolicyBuilder policyBuilder, Action< { if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - //return policyBuilder.RetryForever((outcome, ctx) => onRetry(outcome)); return policyBuilder.RetryForever((Exception outcome, Context ctx) => onRetry(outcome)); } From 285957ceb6dbb0d219425050abccc9b0dc3c57e7 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 12:29:17 +0100 Subject: [PATCH 12/20] Tidy PR471 overloads Eliminates recursive calls which would lead to StackOverflowException. Reduces the number of method-chain hops for certain overloads. --- src/Polly.Shared/Retry/RetrySyntax.cs | 2 +- src/Polly.Shared/Retry/RetrySyntaxAsync.cs | 4 ++-- src/Polly.Shared/Retry/RetryTResultSyntax.cs | 6 +++--- src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Polly.Shared/Retry/RetrySyntax.cs b/src/Polly.Shared/Retry/RetrySyntax.cs index 82588e21bf4..f7d8a57aae2 100644 --- a/src/Polly.Shared/Retry/RetrySyntax.cs +++ b/src/Polly.Shared/Retry/RetrySyntax.cs @@ -563,7 +563,7 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); return policyBuilder.WaitAndRetryForever( - (retryCount, context) => sleepDurationProvider(retryCount), + (retryCount, exception, context) => sleepDurationProvider(retryCount), (exception, i, timespan, context) => onRetry(exception, i, timespan) ); } diff --git a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs index 7b36f6ff2e4..d91a52ba9e8 100644 --- a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs @@ -223,7 +223,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Ac return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, context) => onRetry(outcome, i) #pragma warning restore 1998 ); } @@ -255,7 +255,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder policyBuilder, Fu { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i) => onRetryAsync(outcome, i)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, context) => onRetryAsync(outcome, i)); } /// diff --git a/src/Polly.Shared/Retry/RetryTResultSyntax.cs b/src/Polly.Shared/Retry/RetryTResultSyntax.cs index bb9943a20c3..c54caf98586 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntax.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntax.cs @@ -144,7 +144,7 @@ public static RetryPolicy RetryForever(this PolicyBuilder onRetry(outcome, i)); + return policyBuilder.RetryForever((outcome, i, context) => onRetry(outcome, i)); } /// @@ -616,8 +616,8 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); return policyBuilder.WaitAndRetryForever( - (retryCount, context) => sleepDurationProvider(retryCount), - (exception, i, timespan, context) => onRetry(exception, i, timespan) + (retryCount, outcome, context) => sleepDurationProvider(retryCount), + (outcome, i, timespan, context) => onRetry(outcome, i, timespan) ); } diff --git a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs index 5619b0f339d..63c15113b58 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs @@ -224,7 +224,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder return policyBuilder.RetryForeverAsync( #pragma warning disable 1998 // async method has no awaits, will run synchronously - onRetryAsync: async (outcome, i) => onRetry(outcome, i) + onRetryAsync: async (outcome, i, context) => onRetry(outcome, i) #pragma warning restore 1998 ); } @@ -256,7 +256,7 @@ public static RetryPolicy RetryForeverAsync(this PolicyBuilder { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i) => onRetryAsync(outcome, i)); + return policyBuilder.RetryForeverAsync(onRetryAsync: (outcome, i, context) => onRetryAsync(outcome, i)); } /// From 229f22c0e19fb34afd27a7e93df9a84418947cc4 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 12:30:51 +0100 Subject: [PATCH 13/20] Add unit tests for issue 451 Adds specs on new WaitAnd/RetryForever/Async(...) overloads where the onRetry/Async delegate can take the retryCount as an input parameter. --- .../Retry/RetryForeverAsyncSpecs.cs | 16 ++++++++++++++++ .../Retry/RetryForeverSpecs.cs | 16 ++++++++++++++++ .../Retry/WaitAndRetryForeverAsyncSpecs.cs | 17 +++++++++++++++++ .../Retry/WaitAndRetryForeverSpecs.cs | 17 +++++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs index 47484b79f97..290c1d379af 100644 --- a/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/RetryForeverAsyncSpecs.cs @@ -143,6 +143,22 @@ public void Should_call_onretry_on_each_retry_with_the_passed_context() .ContainValues("value1", "value2"); } + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + + var policy = Policy + .Handle() + .RetryForeverAsync((_, retryCount) => retryCounts.Add(retryCount)); + + policy.RaiseExceptionAsync(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Context_should_be_empty_if_execute_not_called_with_any_data() { diff --git a/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs b/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs index 30669700df2..8dead2a7f07 100644 --- a/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/RetryForeverSpecs.cs @@ -197,6 +197,22 @@ public void Should_call_onretry_on_each_retry_with_the_passed_context() .ContainValues("value1", "value2"); } + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + + var policy = Policy + .Handle() + .RetryForever((_, retryCount) => retryCounts.Add(retryCount)); + + policy.RaiseException(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Should_not_call_onretry_when_no_retries_are_performed() { diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs index cab1fcfeb3c..064d4144d55 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -218,6 +218,23 @@ public async Task Should_call_onretry_on_each_retry_with_the_current_exception() .ContainInOrder(expectedExceptions); } + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + Func provider = i => TimeSpan.Zero; + + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync(provider, (_, retryCount, __) => retryCounts.Add(retryCount)); + + policy.RaiseExceptionAsync(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Should_not_call_onretry_when_no_retries_are_performed() { diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs index 0fbe435b8e6..a7a72a76552 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs @@ -216,6 +216,23 @@ public void Should_call_onretry_on_each_retry_with_the_current_exception() .ContainInOrder(expectedExceptions); } + [Fact] + public void Should_call_onretry_on_each_retry_with_the_current_retry_count() + { + var expectedRetryCounts = new[] { 1, 2, 3 }; + var retryCounts = new List(); + Func provider = i => TimeSpan.Zero; + + var policy = Policy + .Handle() + .WaitAndRetryForever(provider, (_, retryCount, __) => retryCounts.Add(retryCount)); + + policy.RaiseException(3); + + retryCounts.Should() + .ContainInOrder(expectedRetryCounts); + } + [Fact] public void Should_not_call_onretry_when_no_retries_are_performed() { From a9c1bf287047d460d36924e04073ec3ba16472cd Mon Sep 17 00:00:00 2001 From: reisenberger Date: Fri, 29 Jun 2018 07:09:34 +0100 Subject: [PATCH 14/20] Fix issue 472 Fix: generic execute method on non-generic CachePolicy should not throw internally when cache holds no value under the given cache key --- .../Caching/GenericCacheProvider.cs | 6 +- .../Caching/GenericCacheProviderAsync.cs | 6 +- .../Caching/GenericCacheProviderAsyncSpecs.cs | 63 +++++++++++++++++++ .../Caching/GenericCacheProviderSpecs.cs | 55 ++++++++++++++++ .../Polly.SharedSpecs.projitems | 2 + 5 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs create mode 100644 src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs diff --git a/src/Polly.Shared/Caching/GenericCacheProvider.cs b/src/Polly.Shared/Caching/GenericCacheProvider.cs index 51ffb8c3708..61f6f975a95 100644 --- a/src/Polly.Shared/Caching/GenericCacheProvider.cs +++ b/src/Polly.Shared/Caching/GenericCacheProvider.cs @@ -12,14 +12,12 @@ internal class GenericCacheProvider : ISyncCacheProvider.Get(string key) { - return (TCacheFormat) _wrappedCacheProvider.Get(key); + return (TCacheFormat) (_wrappedCacheProvider.Get(key) ?? default(TCacheFormat)); } void ISyncCacheProvider.Put(string key, TCacheFormat value, Ttl ttl) diff --git a/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs b/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs index 5cb371d6027..0499437b425 100644 --- a/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs +++ b/src/Polly.Shared/Caching/GenericCacheProviderAsync.cs @@ -14,14 +14,12 @@ internal class GenericCacheProviderAsync : IAsyncCacheProvider IAsyncCacheProvider.GetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return (TCacheFormat) await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return (TCacheFormat) (await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext) ?? default(TCacheFormat)); } Task IAsyncCacheProvider.PutAsync(string key, TCacheFormat value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) diff --git a/src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs new file mode 100644 index 00000000000..fa5219f9a3c --- /dev/null +++ b/src/Polly.SharedSpecs/Caching/GenericCacheProviderAsyncSpecs.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Polly.Caching; +using Polly.Specs.Helpers; +using Polly.Specs.Helpers.Caching; +using Polly.Utilities; +using Polly.Wrap; +using Xunit; + +namespace Polly.Specs.Caching +{ + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class GenericCacheProviderAsyncSpecs : IDisposable + { + [Fact] + public async Task Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + { + const string operationKey = "SomeOperationKey"; + + bool onErrorCalled = false; + Action onError = (ctx, key, exc) => { onErrorCalled = true; }; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue, onError); + + (await stubCacheProvider.GetAsync(operationKey, CancellationToken.None, false)).Should().BeNull(); + ResultPrimitive result = await cache.ExecuteAsync(async ctx => + { + await TaskHelper.EmptyTask.ConfigureAwait(false); + return ResultPrimitive.Substitute; + }, new Context(operationKey)); + + onErrorCalled.Should().BeFalse(); + } + + [Fact] + public async Task Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + { + const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; + const string operationKey = "SomeOperationKey"; + + IAsyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.CacheAsync(stubCacheProvider, TimeSpan.MaxValue); + + (await stubCacheProvider.GetAsync(operationKey, CancellationToken.None, false)).Should().BeNull(); + + (await cache.ExecuteAsync(async ctx => + { + await TaskHelper.EmptyTask.ConfigureAwait(false); + return ResultPrimitive.Substitute; + }, new Context(operationKey))).Should().Be(valueToReturn); + + (await stubCacheProvider.GetAsync(operationKey, CancellationToken.None, false)).Should().Be(valueToReturn); + } + + public void Dispose() + { + SystemClock.Reset(); + } + } +} diff --git a/src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs b/src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs new file mode 100644 index 00000000000..ab6d6193e7d --- /dev/null +++ b/src/Polly.SharedSpecs/Caching/GenericCacheProviderSpecs.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Polly.Caching; +using Polly.Specs.Helpers; +using Polly.Specs.Helpers.Caching; +using Polly.Utilities; +using Polly.Wrap; +using Xunit; + +namespace Polly.Specs.Caching +{ + [Collection(Polly.Specs.Helpers.Constants.SystemClockDependentTestCollection)] + public class GenericCacheProviderSpecs : IDisposable + { + [Fact] + public void Should_not_error_for_executions_on_non_nullable_types_if_cache_does_not_hold_value() + { + const string operationKey = "SomeOperationKey"; + + bool onErrorCalled = false; + Action onError = (ctx, key, exc) => { onErrorCalled = true; }; + + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue, onError); + + stubCacheProvider.Get(operationKey).Should().BeNull(); + ResultPrimitive result = cache.Execute(ctx => ResultPrimitive.Substitute, new Context(operationKey)); + + onErrorCalled.Should().BeFalse(); + } + + [Fact] + public void Should_execute_delegate_and_put_value_in_cache_for_non_nullable_types_if_cache_does_not_hold_value() + { + const ResultPrimitive valueToReturn = ResultPrimitive.Substitute; + const string operationKey = "SomeOperationKey"; + + ISyncCacheProvider stubCacheProvider = new StubCacheProvider(); + CachePolicy cache = Policy.Cache(stubCacheProvider, TimeSpan.MaxValue); + + stubCacheProvider.Get(operationKey).Should().BeNull(); + + cache.Execute(ctx => { return valueToReturn; }, new Context(operationKey)).Should().Be(valueToReturn); + + stubCacheProvider.Get(operationKey).Should().Be(valueToReturn); + } + + public void Dispose() + { + SystemClock.Reset(); + } + } +} diff --git a/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems b/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems index 57226a4ae79..76534ecf69f 100644 --- a/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems +++ b/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems @@ -24,6 +24,8 @@ + + From af78451bd733147351331255c4e8e49f92ebc437 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sun, 1 Jul 2018 00:00:53 +0100 Subject: [PATCH 15/20] Fix issue 475: SerializingCacheProvider should better handle null and similar cases --- .../Caching/SerializingCacheProvider.cs | 26 ++- .../Caching/SerializingCacheProviderAsync.cs | 30 ++-- .../SerializingCacheProviderAsyncSpecs.cs | 160 +++++++++++++++++- .../Caching/SerializingCacheProviderSpecs.cs | 159 +++++++++++++++++ 4 files changed, 344 insertions(+), 31 deletions(-) diff --git a/src/Polly.Shared/Caching/SerializingCacheProvider.cs b/src/Polly.Shared/Caching/SerializingCacheProvider.cs index e1909219d94..e30ba11d669 100644 --- a/src/Polly.Shared/Caching/SerializingCacheProvider.cs +++ b/src/Polly.Shared/Caching/SerializingCacheProvider.cs @@ -20,11 +20,8 @@ public class SerializingCacheProvider : ISyncCacheProvider /// serializer public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -34,7 +31,8 @@ public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProv /// The value from cache; or null, if none was found. public object Get(string key) { - return _serializer.Deserialize(_wrappedCacheProvider.Get(key)); + TSerialized objectToDeserialize = _wrappedCacheProvider.Get(key); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? null : _serializer.Deserialize(objectToDeserialize); } /// @@ -45,7 +43,8 @@ public object Get(string key) /// The time-to-live for the cache entry. public void Put(string key, object value, Ttl ttl) { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + if (value != null) + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); } } @@ -69,11 +68,8 @@ public class SerializingCacheProvider : ISyncCacheProvider /// serializer public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -83,7 +79,8 @@ public SerializingCacheProvider(ISyncCacheProvider wrappedCacheProv /// The value from cache; or null, if none was found. public TResult Get(string key) { - return _serializer.Deserialize(_wrappedCacheProvider.Get(key)); + TSerialized objectToDeserialize = _wrappedCacheProvider.Get(key); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? default(TResult) : _serializer.Deserialize(objectToDeserialize); } /// @@ -94,7 +91,8 @@ public TResult Get(string key) /// The time-to-live for the cache entry. public void Put(string key, TResult value, Ttl ttl) { - _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); + if (value != null && !value.Equals(default(TResult))) + _wrappedCacheProvider.Put(key, _serializer.Serialize(value), ttl); } } diff --git a/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs b/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs index 788fbea802c..14d3908d7fd 100644 --- a/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs +++ b/src/Polly.Shared/Caching/SerializingCacheProviderAsync.cs @@ -22,11 +22,8 @@ public class SerializingCacheProviderAsync : IAsyncCacheProvider /// serializer public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -38,8 +35,8 @@ public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCac /// A promising as Result the value from cache; or null, if none was found. public async Task GetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return _serializer.Deserialize( - await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext)); + TSerialized objectToDeserialize = await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? null :_serializer.Deserialize(objectToDeserialize); } /// @@ -54,7 +51,9 @@ public async Task GetAsync(string key, CancellationToken cancellationTok public async Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { - await _wrappedCacheProvider.PutAsync( + if (value != null) + + await _wrappedCacheProvider.PutAsync( key, _serializer.Serialize(value), ttl, @@ -83,11 +82,8 @@ public class SerializingCacheProviderAsync : IAsyncCachePr /// serializer public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCacheProvider, ICacheItemSerializer serializer) { - if (wrappedCacheProvider == null) throw new ArgumentNullException(nameof(wrappedCacheProvider)); - if (serializer == null) throw new ArgumentNullException(nameof(serializer)); - - _wrappedCacheProvider = wrappedCacheProvider; - _serializer = serializer; + _wrappedCacheProvider = wrappedCacheProvider ?? throw new ArgumentNullException(nameof(wrappedCacheProvider)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } /// @@ -99,8 +95,8 @@ public SerializingCacheProviderAsync(IAsyncCacheProvider wrappedCac /// A promising as Result the value from cache; or null, if none was found. public async Task GetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) { - return _serializer.Deserialize( - await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext)); + TSerialized objectToDeserialize = await _wrappedCacheProvider.GetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext); + return objectToDeserialize == null || objectToDeserialize.Equals(default(TSerialized)) ? default(TResult) : _serializer.Deserialize(objectToDeserialize); } /// @@ -115,7 +111,9 @@ public async Task GetAsync(string key, CancellationToken cancellationTo public async Task PutAsync(string key, TResult value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) { - await _wrappedCacheProvider.PutAsync( + if (value != null && !value.Equals(default(TResult))) + + await _wrappedCacheProvider.PutAsync( key, _serializer.Serialize(value), ttl, diff --git a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs index d8e27b9d337..ac2c75a605e 100644 --- a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderAsyncSpecs.cs @@ -65,6 +65,25 @@ public async Task Single_generic_SerializingCacheProvider_should_serialize_on_pu .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default(object); + string key = "some key"; + + SerializingCacheProviderAsync serializingCacheProvider = new SerializingCacheProviderAsync(stubCacheProvider.AsyncFor(), stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -87,6 +106,26 @@ public async Task Single_generic_SerializingCacheProvider_should_deserialize_on_ fromCache.Should().Be(objectToCache); } + [Fact] + public async Task Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync serializingCacheProvider = new SerializingCacheProviderAsync(stubCacheProvider.AsyncFor(), stubSerializer); + object fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -107,6 +146,25 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default(object); + string key = "some key"; + + SerializingCacheProviderAsync serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -128,6 +186,26 @@ public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_ fromCache.Should().Be(objectToCache); } + [Fact] + public async Task Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync serializingCacheProvider = stubCacheProvider.AsyncFor().WithSerializer(stubSerializer); + object fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + #endregion #region TResult-to-TSerialized serializer @@ -184,6 +262,25 @@ public async Task Double_generic_SerializingCacheProvider_should_serialize_on_pu .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default(ResultPrimitive); + string key = "some key"; + + SerializingCacheProviderAsync> serializingCacheProvider = new SerializingCacheProviderAsync>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -205,6 +302,26 @@ public async Task Double_generic_SerializingCacheProvider_should_deserialize_on_ fromCache.Should().Be(objectToCache); } + [Fact] + public async Task Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync> serializingCacheProvider = new SerializingCacheProviderAsync>(stubCacheProvider.AsyncFor>(), stubTResultSerializer); + ResultPrimitive fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -226,6 +343,26 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ .Which.Original.Should().Be(objectToCache); } + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default(ResultPrimitive); + string key = "some key"; + + SerializingCacheProviderAsync> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + await serializingCacheProvider.PutAsync(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -242,12 +379,33 @@ public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_ stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); await stubCacheProvider.PutAsync(key, new StubSerialized(objectToCache), new Ttl(TimeSpan.FromMinutes(1)), CancellationToken.None, false); - object fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + ResultPrimitive fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); deserializeInvoked.Should().Be(true); fromCache.Should().Be(objectToCache); } + [Fact] + public async Task Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProviderAsync> serializingCacheProvider = + stubCacheProvider.AsyncFor>().WithSerializer(stubTResultSerializer); + ResultPrimitive fromCache = await serializingCacheProvider.GetAsync(key, CancellationToken.None, false); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + #endregion } } \ No newline at end of file diff --git a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs index a77b189c4f9..d8c4c2c191f 100644 --- a/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs +++ b/src/Polly.SharedSpecs/Caching/SerializingCacheProviderSpecs.cs @@ -63,6 +63,25 @@ public void Single_generic_SerializingCacheProvider_should_serialize_on_put() .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Single_generic_SerializingCacheProvider_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default(object); + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -85,6 +104,26 @@ public void Single_generic_SerializingCacheProvider_should_deserialize_on_get() fromCache.Should().Be(objectToCache); } + [Fact] + public void Single_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProvider serializingCacheProvider = new SerializingCacheProvider(stubCacheProvider.For(), stubSerializer); + object fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -105,6 +144,25 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + object objectToCache = default(object); + string key = "some key"; + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -126,6 +184,26 @@ public void Single_generic_SerializingCacheProvider_from_extension_syntax_should fromCache.Should().Be(objectToCache); } + [Fact] + public void Single_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer stubSerializer = new StubSerializer( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProvider serializingCacheProvider = stubCacheProvider.For().WithSerializer(stubSerializer); + object fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(object)); + } + #endregion #region TResult-to-TSerialized serializer @@ -182,6 +260,25 @@ public void Double_generic_SerializingCacheProvider_should_serialize_on_put() .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Double_generic_SerializingCacheProvider_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default(ResultPrimitive); + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() { @@ -203,6 +300,26 @@ public void Double_generic_SerializingCacheProvider_should_deserialize_on_get() fromCache.Should().Be(objectToCache); } + [Fact] + public void Double_generic_SerializingCacheProvider_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProvider> serializingCacheProvider = new SerializingCacheProvider>(stubCacheProvider.For>(), stubTResultSerializer); + ResultPrimitive fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_serialize_on_put() { @@ -224,6 +341,27 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should .Which.Original.Should().Be(objectToCache); } + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_serialize_on_put_for_defaultTResult() + { + bool serializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => { serializeInvoked = true; return new StubSerialized(o); }, + deserialize: s => s.Original + ); + StubCacheProvider stubCacheProvider = new StubCacheProvider(); + ResultPrimitive objectToCache = default(ResultPrimitive); + string key = "some key"; + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + + serializingCacheProvider.Put(key, objectToCache, new Ttl(TimeSpan.FromMinutes(1))); + + serializeInvoked.Should().Be(false); + stubCacheProvider.Get(key).Should().BeNull(); + } + [Fact] public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_deserialize_on_get() { @@ -246,6 +384,27 @@ public void Double_generic_SerializingCacheProvider_from_extension_syntax_should fromCache.Should().Be(objectToCache); } + [Fact] + public void Double_generic_SerializingCacheProvider_from_extension_syntax_should_not_deserialize_on_get_when_item_not_in_cache() + { + bool deserializeInvoked = false; + StubSerializer> stubTResultSerializer = new StubSerializer>( + serialize: o => new StubSerialized(o), + deserialize: s => { deserializeInvoked = true; return s.Original; } + ); + var stubCacheProvider = new StubCacheProvider(); + string key = "some key"; + + stubCacheProvider.Get(key).Should().BeNull(); + + SerializingCacheProvider> serializingCacheProvider = + stubCacheProvider.For>().WithSerializer(stubTResultSerializer); + ResultPrimitive fromCache = serializingCacheProvider.Get(key); + + deserializeInvoked.Should().Be(false); + fromCache.Should().Be(default(ResultPrimitive)); + } + #endregion } } From 3e03c896b8060d237de7358c986886fa4240f226 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 13:12:36 +0100 Subject: [PATCH 16/20] Tidying and consistency Minor tidying and consistency in the timeout syntax classes --- src/Polly.Shared/Timeout/TimeoutEngineAsync.cs | 6 +++--- src/Polly.Shared/Timeout/TimeoutSyntax.cs | 11 +---------- src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs | 12 +----------- src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs | 11 +---------- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs b/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs index 6f2539f3f30..448b219e349 100644 --- a/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutEngineAsync.cs @@ -51,12 +51,12 @@ internal static async Task ImplementationAsync( .WhenAny(actionTask, timeoutTask).ConfigureAwait(continueOnCapturedContext)).ConfigureAwait(continueOnCapturedContext); } - catch (Exception e) + catch (Exception ex) { if (timeoutCancellationTokenSource.IsCancellationRequested) { - await onTimeoutAsync(context, timeout, actionTask, e).ConfigureAwait(continueOnCapturedContext); - throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", e); + await onTimeoutAsync(context, timeout, actionTask, ex).ConfigureAwait(continueOnCapturedContext); + throw new TimeoutRejectedException("The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.", ex); } throw; diff --git a/src/Polly.Shared/Timeout/TimeoutSyntax.cs b/src/Polly.Shared/Timeout/TimeoutSyntax.cs index 56123ac42dd..d4b1289ed60 100644 --- a/src/Polly.Shared/Timeout/TimeoutSyntax.cs +++ b/src/Polly.Shared/Timeout/TimeoutSyntax.cs @@ -357,18 +357,9 @@ public static TimeoutPolicy Timeout(Func timeoutProvider, Act /// onTimeout public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - return new TimeoutPolicy( - (action, context, cancellationToken) => TimeoutEngine.Implementation( - (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, - context, - cancellationToken, - timeoutProvider, - timeoutStrategy, - (ctx, timeout, task, ex) => onTimeout(ctx, timeout, task)) - ); + return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, ex) => onTimeout(ctx, timeout, task)); } /// diff --git a/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs b/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs index 6b5a38f46c1..f14ebc2c684 100644 --- a/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutSyntaxAsync.cs @@ -362,19 +362,9 @@ public static TimeoutPolicy TimeoutAsync(Func timeoutProvider /// onTimeoutAsync public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - return new TimeoutPolicy( - (action, context, cancellationToken, continueOnCapturedContext) => TimeoutEngine.ImplementationAsync( - async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - context, - timeoutProvider, - timeoutStrategy, - (ctx, timeout, task, exception) => onTimeoutAsync(context, timeout, task), - cancellationToken, - continueOnCapturedContext) - ); + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, ex) => onTimeoutAsync(ctx, timeout, task)); } /// diff --git a/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs b/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs index 0c4b98dad27..f28c4430845 100644 --- a/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs +++ b/src/Polly.Shared/Timeout/TimeoutTResultSyntax.cs @@ -362,18 +362,9 @@ public static TimeoutPolicy Timeout(Func ti /// onTimeout public static TimeoutPolicy Timeout(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Action onTimeout) { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeout == null) throw new ArgumentNullException(nameof(onTimeout)); - return new TimeoutPolicy( - (action, context, cancellationToken) => TimeoutEngine.Implementation( - action, - context, - cancellationToken, - timeoutProvider, - timeoutStrategy, - (ctx, timeout, task, ex) => onTimeout(ctx, timeout, task)) - ); + return Timeout(timeoutProvider, timeoutStrategy, (ctx, timeout, task, ex) => onTimeout(ctx, timeout, task)); } /// From 37005afd53bd8c349d2560594e6894a82266438d Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 13:13:13 +0100 Subject: [PATCH 17/20] Tidying and consistency Minor tidying and consistency in the timeout syntax classes --- .../Timeout/TimeoutTResultSyntaxAsync.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs b/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs index 340ad58be78..7791b0cfb2f 100644 --- a/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Timeout/TimeoutTResultSyntaxAsync.cs @@ -359,19 +359,9 @@ public static TimeoutPolicy TimeoutAsync(FunconTimeoutAsync public static TimeoutPolicy TimeoutAsync(Func timeoutProvider, TimeoutStrategy timeoutStrategy, Func onTimeoutAsync) { - if (timeoutProvider == null) throw new ArgumentNullException(nameof(timeoutProvider)); if (onTimeoutAsync == null) throw new ArgumentNullException(nameof(onTimeoutAsync)); - return new TimeoutPolicy( - (action, context, cancellationToken, continueOnCapturedContext) => TimeoutEngine.ImplementationAsync( - action, - context, - timeoutProvider, - timeoutStrategy, - (ctx, timeout, task, exception) => onTimeoutAsync(ctx, timeout, task), - cancellationToken, - continueOnCapturedContext) - ); + return TimeoutAsync(timeoutProvider, timeoutStrategy, (ctx, timeout, task, ex) => onTimeoutAsync(ctx, timeout, task)); } /// From 328faa6138c9907dd424cb612bdfa3654a0c3a20 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 13:14:28 +0100 Subject: [PATCH 18/20] Rationalise timeout tests for 338 Rationalise timeout tests for 338 (make the only new tests those which specifically test the new functionality) --- .../Timeout/TimeoutAsyncSpecs.cs | 367 ++--------------- src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs | 270 +------------ .../Timeout/TimeoutTResultAsyncSpecs.cs | 376 ++---------------- .../Timeout/TimeoutTResultSpecs.cs | 327 ++------------- 4 files changed, 104 insertions(+), 1236 deletions(-) diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs index eed53354d5a..31af90fc622 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutAsyncSpecs.cs @@ -593,9 +593,33 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); } + + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - #endregion + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + + #endregion #region onTimeout overload - optimistic @@ -739,37 +763,8 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - #endregion - - - #region onTimeout full argument list - pessimistic - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - - })) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - [Fact] - public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pessimistic() + public void Should_call_ontimeout_with_timing_out_exception__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -780,174 +775,6 @@ public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pe return TaskHelper.EmptyTask; }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - - })) - .ShouldThrow(); - - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__pessimistic() - { - string operationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - - }, contextPassedToExecute)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - })) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - }, context)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - - })) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().NotBeNull(); - } - - [Fact] - public async Task Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. - // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - - Exception exceptionToThrow = new DivideByZeroException(); - - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. - return TaskHelper.EmptyTask; - }; - - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); - throw exceptionToThrow; - })) - .ShouldThrow(); - - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - - } - - #endregion - - #region onTimeout full argument list - optimistic - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; @@ -958,151 +785,11 @@ public void Should_call_ontimeout_full_argument_list_with_configured_timeout__op }, userCancellationToken).ConfigureAwait(false)) .ShouldThrow(); - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_non_null_exception__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() - { - string opeationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(opeationKey); - - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - - }, contextPassedToExecute, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(opeationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - - }, context, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().BeNull(); - } - -#endregion + #endregion } } diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs index b6983fbe35d..b0f366fdd40 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutSpecs.cs @@ -598,6 +598,23 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + #endregion #region onTimeout overload - optimistic @@ -702,174 +719,8 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - - #endregion - - #region onTimeout with full argument list - pessimistic - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_not_null_exception__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; - - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .ShouldThrow(); - - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__pessimistic() - { - string operationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute((ctx) => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), contextPassedToExecute)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_full_argument_list_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_with_timeout_full_argument_list_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - - policy.Invoking(p => p.Execute((ctx) => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None), context)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None))) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().NotBeNull(); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. - // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - - Exception exceptionToThrow = new DivideByZeroException(); - - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); - }; - - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - throw exceptionToThrow; - })) - .ShouldThrow(); - - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - - } - - #endregion - - #region onTimeout with full argument list - optimistic - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(1), ct), userCancellationToken)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_not_null_exception__optimistic() + public void Should_call_ontimeout_with_timing_out_exception__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -886,91 +737,6 @@ public void Should_call_ontimeout_full_argument_list_with_not_null_exception__op exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() - { - string opeationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(opeationKey); - - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute((ctx, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), contextPassedToExecute, userCancellationToken)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(opeationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - - var userCancellationToken = CancellationToken.None; - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; - - policy.Invoking(p => p.Execute((ctx, ct) => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), context, userCancellationToken)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => SystemClock.Sleep(TimeSpan.FromSeconds(3), ct), userCancellationToken)) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().BeNull(); - } - - #endregion - } } diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs index cc8c8705601..4fc4dd5c643 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultAsyncSpecs.cs @@ -595,6 +595,31 @@ public async Task Should_call_ontimeout_with_task_wrapping_abandoned_action_allo } + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Func onTimeoutAsync = (ctx, span, task, exception) => + { + exceptionPassedToOnTimeout = exception; + return TaskHelper.EmptyTask; + }; + + var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); + + policy.Awaiting(async p => await p.ExecuteAsync(async () => + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } + #endregion #region onTimeout overload - optimistic @@ -740,37 +765,8 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - - #endregion - - #region onTimeout full argument list - pessimistic - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - [Fact] - public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pessimistic() + public void Should_call_ontimeout_with_timing_out_exception__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); @@ -781,330 +777,20 @@ public void Should_call_ontimeout_full_argument_list_with_non_null_exception__pe return TaskHelper.EmptyTask; }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_llist_with_passed_context__pessimistic() - { - string operationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, context)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().NotBeNull(); - } - - [Fact] - public async Task Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeoutAsync. - // That means we can't use the SystemClock.SleepAsync(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .WhenAny() in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.WhenAny() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - - Exception exceptionToThrow = new DivideByZeroException(); - - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = task.Exception.InnerException); // Intentionally not awaited: we want to assign the continuation, but let it run in its own time when the executed delegate eventually completes. - return TaskHelper.EmptyTask; - }; - - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.TimeoutAsync(shimTimespan, TimeoutStrategy.Pessimistic, onTimeoutAsync); - - policy.Awaiting(async p => await p.ExecuteAsync(async () => - { - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); - throw exceptionToThrow; - })) - .ShouldThrow(); - - await SystemClock.SleepAsync(thriceShimTimeSpan, CancellationToken.None).ConfigureAwait(false); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - } - - #endregion - - #region onTimeout full argument list - optimistic - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); var userCancellationToken = CancellationToken.None; policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_non_null_exception__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - exceptionPassedToOnTimeout = exception; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); + { + await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken).ConfigureAwait(false)) + .ShouldThrow(); exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() - { - string operationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - contextPassedToOnTimeout = ctx; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, Exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutFunc, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - timeoutPassedToOnTimeout = span; - return TaskHelper.EmptyTask; - }; - - var policy = Policy.TimeoutAsync(timeoutProvider, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; - - policy.Awaiting(async p => await p.ExecuteAsync(async (ctx, ct) => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, context, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Func onTimeoutAsync = (ctx, span, task, exception) => - { - taskPassedToOnTimeout = task; - return TaskHelper.EmptyTask; - }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.TimeoutAsync(timeout, TimeoutStrategy.Optimistic, onTimeoutAsync); - var userCancellationToken = CancellationToken.None; - - policy.Awaiting(async p => await p.ExecuteAsync(async ct => - { - await SystemClock.SleepAsync(TimeSpan.FromSeconds(3), ct).ConfigureAwait(false); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken).ConfigureAwait(false)) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().BeNull(); - } - - #endregion } diff --git a/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs b/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs index f48c67ba162..9b761ad3c05 100644 --- a/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs +++ b/src/Polly.SharedSpecs/Timeout/TimeoutTResultSpecs.cs @@ -642,6 +642,27 @@ public void Should_call_ontimeout_with_task_wrapping_abandoned_action_allowing_c exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); } + + [Fact] + public void Should_call_ontimeout_with_timing_out_exception__pessimistic() + { + TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); + + Exception exceptionPassedToOnTimeout = null; + Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; + + var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); + + policy.Invoking(p => p.Execute(() => + { + SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); + return ResultPrimitive.WhateverButTooLate; + })) + .ShouldThrow(); + + exceptionPassedToOnTimeout.Should().NotBeNull(); + exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); + } #endregion @@ -767,321 +788,29 @@ public void Should_call_ontimeout_but_not_with_task_wrapping_abandoned_action__o taskPassedToOnTimeout.Should().BeNull(); } - #endregion - - - #region onTimeout full argument list - pessimistic - - [Fact] - public void Should_call_ontimeout_full_argument_llist_with_configured_timeout__pessimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - [Fact] - public void Should_call_ontimeout_full_argument_llist_with_non_null_exception__pessimistic() + public void Should_call_ontimeout_with_timing_out_exception__optimistic() { TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); Exception exceptionPassedToOnTimeout = null; Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - exceptionPassedToOnTimeout.Should().NotBeNull(); - exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__pessimistic() - { - string operationKey = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(operationKey); - - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute((ctx) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKey); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__pessimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__pessimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Pessimistic, onTimeout); - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") { ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) }; - - policy.Invoking(p => p.Execute((ctx) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - }, context)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action__pessimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), CancellationToken.None); - return ResultPrimitive.WhateverButTooLate; - })) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().NotBeNull(); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_task_wrapping_abandoned_action_allowing_capture_of_otherwise_unobserved_exception__pessimistic() - { - SystemClock.Reset(); // This is the only test which cannot work with the artificial SystemClock of TimeoutSpecsBase. We want the invoked delegate to continue as far as: throw exceptionToThrow, to genuinely check that the walked-away-from task throws that, and that we pass it to onTimeout. - // That means we can't use the SystemClock.Sleep(...) within the executed delegate to artificially trigger the timeout cancellation (as for example the test above does). - // In real execution, it is the .Wait(timeoutCancellationTokenSource.Token) in the timeout implementation which throws for the timeout. We don't want to go as far as abstracting Task.Wait() out into SystemClock, so we let this test run at real-world speed, not abstracted-clock speed. - - Exception exceptionToThrow = new DivideByZeroException(); - - Exception exceptionObservedFromTaskPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => - { - task.ContinueWith(t => exceptionObservedFromTaskPassedToOnTimeout = t.Exception.InnerException); - }; - - TimeSpan shimTimespan = TimeSpan.FromSeconds(1); // Consider increasing shimTimeSpan if test fails transiently in different environments. - TimeSpan thriceShimTimeSpan = shimTimespan + shimTimespan + shimTimespan; - var policy = Policy.Timeout(shimTimespan, TimeoutStrategy.Pessimistic, onTimeout); - - policy.Invoking(p => p.Execute(() => - { - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - throw exceptionToThrow; - })) - .ShouldThrow(); - - SystemClock.Sleep(thriceShimTimeSpan, CancellationToken.None); - exceptionObservedFromTaskPassedToOnTimeout.Should().NotBeNull(); - exceptionObservedFromTaskPassedToOnTimeout.Should().Be(exceptionToThrow); - - } - - #endregion - - #region onTimeout full argument list - optimistic - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_configured_timeout__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); var userCancellationToken = CancellationToken.None; policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutPassedToConfiguration); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_with_non_null_exception__optimistic() - { - TimeSpan timeoutPassedToConfiguration = TimeSpan.FromMilliseconds(250); - - Exception exceptionPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { exceptionPassedToOnTimeout = exception; }; - - var policy = Policy.Timeout(timeoutPassedToConfiguration, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .ShouldThrow(); + { + SystemClock.Sleep(TimeSpan.FromSeconds(1), ct); + return ResultPrimitive.WhateverButTooLate; + }, userCancellationToken)) + .ShouldThrow(); exceptionPassedToOnTimeout.Should().NotBeNull(); exceptionPassedToOnTimeout.Should().BeOfType(typeof(OperationCanceledException)); } - [Fact] - public void Should_call_ontimeout_full_argument_list_with_passed_context__optimistic() - { - string operationKeyy = Guid.NewGuid().ToString(); - Context contextPassedToExecute = new Context(operationKeyy); - - Context contextPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { contextPassedToOnTimeout = ctx; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute((ctx, ct) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, contextPassedToExecute, userCancellationToken)) - .ShouldThrow(); - - contextPassedToOnTimeout.Should().NotBeNull(); - contextPassedToOnTimeout.OperationKey.Should().Be(operationKeyy); - contextPassedToOnTimeout.Should().BeSameAs(contextPassedToExecute); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func__optimistic(int programaticallyControlledDelay) - { - Func timeoutFunc = () => TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay); - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - - var policy = Policy.Timeout(timeoutFunc, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutFunc()); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Should_call_ontimeout_full_argument_list_with_timeout_supplied_different_for_each_execution_by_evaluating_func_influenced_by_context__optimistic(int programaticallyControlledDelay) - { - Func timeoutProvider = ctx => (TimeSpan)ctx["timeout"]; - - TimeSpan? timeoutPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { timeoutPassedToOnTimeout = span; }; - var policy = Policy.Timeout(timeoutProvider, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - // Supply a programatically-controlled timeout, via the execution context. - Context context = new Context("SomeOperationKey") - { - ["timeout"] = TimeSpan.FromMilliseconds(25 * programaticallyControlledDelay) - }; - - policy.Invoking(p => p.Execute((ctx, ct) => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, context, userCancellationToken)) - .ShouldThrow(); - - timeoutPassedToOnTimeout.Should().Be(timeoutProvider(context)); - } - - [Fact] - public void Should_call_ontimeout_full_argument_list_but_not_with_task_wrapping_abandoned_action__optimistic() - { - Task taskPassedToOnTimeout = null; - Action onTimeout = (ctx, span, task, exception) => { taskPassedToOnTimeout = task; }; - - TimeSpan timeout = TimeSpan.FromMilliseconds(250); - var policy = Policy.Timeout(timeout, TimeoutStrategy.Optimistic, onTimeout); - var userCancellationToken = CancellationToken.None; - - policy.Invoking(p => p.Execute(ct => - { - SystemClock.Sleep(TimeSpan.FromSeconds(3), ct); - return ResultPrimitive.WhateverButTooLate; - }, userCancellationToken)) - .ShouldThrow(); - - taskPassedToOnTimeout.Should().BeNull(); - } - #endregion + } } From 61e42f134416399718c693bc2cebb37643971013 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 16:50:41 +0100 Subject: [PATCH 19/20] Fix Context.PolicyKey issue 463 Fix Context.PolicyKey such that, as execution bubbles back outwards through a PolicyWrap, Context.PolicyKey correctly adopts the value of the policies in play during the outward journey through the wrap. --- .../Policy.Async.ExecuteOverloads.cs | 24 ++++++-- .../Policy.Async.TResult.ExecuteOverloads.cs | 12 +++- src/Polly.Shared/Policy.ContextAndKeys.cs | 19 ++++++- src/Polly.Shared/Policy.ExecuteOverloads.cs | 22 ++++++-- .../Policy.TResult.ExecuteOverloads.cs | 12 +++- .../Wrap/PolicyWrap.ContextAndKeys.cs | 18 ++++-- .../Wrap/PolicyWrapContextAndKeySpecs.cs | 54 ++++++++++++++++++ .../Wrap/PolicyWrapContextAndKeySpecsAsync.cs | 56 +++++++++++++++++++ 8 files changed, 200 insertions(+), 17 deletions(-) diff --git a/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs b/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs index 32f04cdd891..9a1556b6c5a 100644 --- a/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.Async.ExecuteOverloads.cs @@ -117,9 +117,17 @@ public Task ExecuteAsync(Func action, IDiction public Task ExecuteAsync(Func action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #region Overloads method-generic in TResult @@ -245,9 +253,17 @@ public Task ExecuteAsync(Func ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs b/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs index 3b50b32be34..8d6033297fd 100644 --- a/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.Async.TResult.ExecuteOverloads.cs @@ -128,9 +128,17 @@ public Task ExecuteAsync(Func public Task ExecuteAsync(Func> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteAsyncInternal(action, context, cancellationToken, continueOnCapturedContext); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Policy.ContextAndKeys.cs b/src/Polly.Shared/Policy.ContextAndKeys.cs index 21d980b898a..c107474276e 100644 --- a/src/Polly.Shared/Policy.ContextAndKeys.cs +++ b/src/Polly.Shared/Policy.ContextAndKeys.cs @@ -21,10 +21,27 @@ public abstract partial class PolicyBase /// Updates the execution with context from the executing . /// /// The execution . - internal virtual void SetPolicyContext(Context executionContext) + /// The prior to changes by this method. + /// The prior to changes by this method. + internal virtual void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) // priorPolicyWrapKey and priorPolicyKey could be handled as a (string, string) System.ValueTuple return type instead of out parameters, when our minimum supported target supports this. { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; + executionContext.PolicyKey = PolicyKey; } + + /// + /// Restores the supplied keys to the execution . + /// + /// The execution . + /// The prior to execution through this policy. + /// The prior to execution through this policy. + internal void RestorePolicyContext(Context executionContext, string priorPolicyWrapKey, string priorPolicyKey) + { + executionContext.PolicyWrapKey = priorPolicyWrapKey; + executionContext.PolicyKey = priorPolicyKey; + } } public abstract partial class Policy diff --git a/src/Polly.Shared/Policy.ExecuteOverloads.cs b/src/Polly.Shared/Policy.ExecuteOverloads.cs index b3422564ce5..d7c2d23cc95 100644 --- a/src/Polly.Shared/Policy.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.ExecuteOverloads.cs @@ -77,9 +77,16 @@ public void Execute(Action action, Context context, { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - ExecuteInternal(action, context, cancellationToken); + try + { + ExecuteInternal(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #region Overloads method-generic in TResult @@ -171,9 +178,16 @@ public TResult Execute(Func action { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); - return ExecuteInternal(action, context, cancellationToken); + try + { + return ExecuteInternal(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs b/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs index b97541907a9..6dab4628c9e 100644 --- a/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs +++ b/src/Polly.Shared/Policy.TResult.ExecuteOverloads.cs @@ -92,9 +92,17 @@ public TResult Execute(Func action, IDictio public TResult Execute(Func action, Context context, CancellationToken cancellationToken) { if (context == null) throw new ArgumentNullException(nameof(context)); - SetPolicyContext(context); - return ExecuteInternal(action, context, cancellationToken); + SetPolicyContext(context, out string priorPolicyWrapKey, out string priorPolicyKey); + + try + { + return ExecuteInternal(action, context, cancellationToken); + } + finally + { + RestorePolicyContext(context, priorPolicyWrapKey, priorPolicyKey); + } } #endregion diff --git a/src/Polly.Shared/Wrap/PolicyWrap.ContextAndKeys.cs b/src/Polly.Shared/Wrap/PolicyWrap.ContextAndKeys.cs index f1bf8f2fa07..1a97f1f604d 100644 --- a/src/Polly.Shared/Wrap/PolicyWrap.ContextAndKeys.cs +++ b/src/Polly.Shared/Wrap/PolicyWrap.ContextAndKeys.cs @@ -10,11 +10,16 @@ public partial class PolicyWrap /// Updates the execution with context from the executing . /// /// The execution . - internal override void SetPolicyContext(Context executionContext) + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext); + base.SetPolicyContext(executionContext, out _, out _); } } @@ -24,11 +29,16 @@ public partial class PolicyWrap /// Updates the execution with context from the executing . /// /// The execution . - internal override void SetPolicyContext(Context executionContext) + /// The prior to changes by this method. + /// The prior to changes by this method. + internal override void SetPolicyContext(Context executionContext, out string priorPolicyWrapKey, out string priorPolicyKey) { + priorPolicyWrapKey = executionContext.PolicyWrapKey; + priorPolicyKey = executionContext.PolicyKey; + if (executionContext.PolicyWrapKey == null) executionContext.PolicyWrapKey = PolicyKey; - base.SetPolicyContext(executionContext); + base.SetPolicyContext(executionContext, out _, out _); } } } diff --git a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs index e84e87470f2..db910ec4ce2 100644 --- a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs +++ b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs @@ -59,6 +59,33 @@ public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_Policy policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); } + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + ISyncPolicy fallback = Policy + .Handle() + .Fallback(_ => {}, onFallback: (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (result, retryCount, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + ISyncPolicy policyWrap = Policy.Wrap(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.Execute(() => throw new Exception()); + } + [Fact] public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() { @@ -189,6 +216,33 @@ public void Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_Policy policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); } + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + ISyncPolicy fallback = Policy + .Handle() + .Fallback(ResultPrimitive.Undefined, onFallback: (result, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + }) + .WithPolicyKey("FallbackPolicy"); + + ISyncPolicy retry = Policy + .Handle() + .Retry(1, onRetry: (result, retryCount, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + Policy policyWrap = Policy.Wrap(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.Execute(() => throw new Exception()); + } + [Fact] public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_wrap() { diff --git a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs index cd483141ecb..c203da0cd4d 100644 --- a/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs +++ b/src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs @@ -61,6 +61,34 @@ public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_ policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); } + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_,__) => TaskHelper.EmptyTask, (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (result, retryCount, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.ExecuteAsync(() => throw new Exception()); + } + [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { @@ -192,6 +220,34 @@ public async Task Should_pass_PolicyKey_to_execution_context_of_inner_policy_as_ policyWrapKeySetOnExecutionContext.Should().Be(wrapKey); } + [Fact] + public void Should_restore_PolicyKey_of_outer_policy_to_execution_context_as_move_outwards_through_PolicyWrap() + { + IAsyncPolicy fallback = Policy + .Handle() + .FallbackAsync((_, __) => Task.FromResult(ResultPrimitive.Undefined), (_, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("FallbackPolicy"); + return TaskHelper.EmptyTask; + }) + .WithPolicyKey("FallbackPolicy"); + + IAsyncPolicy retry = Policy + .Handle() + .RetryAsync(1, onRetry: (result, retryCount, context) => + { + context.PolicyWrapKey.Should().Be("PolicyWrap"); + context.PolicyKey.Should().Be("RetryPolicy"); + }) + .WithPolicyKey("RetryPolicy"); + + IAsyncPolicy policyWrap = Policy.WrapAsync(fallback, retry) + .WithPolicyKey("PolicyWrap"); + + policyWrap.ExecuteAsync(() => throw new Exception()); + } + [Fact] public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_ignoring_inner_PolicyWrap_keys_even_when_executing_policies_in_inner_WrapAsync() { From 21130f8809c8d7465f4d5b80a90f98bf76aef0e6 Mon Sep 17 00:00:00 2001 From: reisenberger Date: Sat, 14 Jul 2018 17:17:34 +0100 Subject: [PATCH 20/20] Add v6.1.0 doco --- CHANGELOG.md | 6 +++++- README.md | 2 ++ src/Polly.nuspec | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46bd0275a7b..ac1fe0d0e37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 6.1.0 -- Improved cache error message (issue 455) +- Bug Fix: Context.PolicyKey behaviour in PolicyWrap (issue 463) +- Bug Fix: CachePolicy behaviour with non-nullable types (issues 472, 475) +- Enhancement: WaitAnd/RetryForever overloads where onRetry takes the retry number as a parameter (issue 451) +- Enhancement: Overloads where onTimeout takes thrown exception as a parameter (issue 338) +- Enhancement: Improved cache error message (issue 455) ## 6.0.1 - Version 6 RTM, for integration to ASPNET Core 2.1 IHttpClientFactory diff --git a/README.md b/README.md index 95ff0df4586..f30acbad39e 100644 --- a/README.md +++ b/README.md @@ -990,6 +990,8 @@ For full detailed of supported targets by version, see [supported targets](https * [@benagain](https://github.com/benagain) - Bug fix: RelativeTtl in CachePolicy now always returns a ttl relative to time item is cached. * [@urig](https://github.com/urig) - Allow TimeoutPolicy to be configured with Timeout.InfiniteTimeSpan. * [@reisenberger](https://github.com/reisenberger) - Integration with [IHttpClientFactory](https://github.com/aspnet/HttpClientFactory/) for ASPNET Core 2.1. +* [@freakazoid182](https://github.com/Freakazoid182) - WaitAnd/RetryForever overloads where onRetry takes the retry number as a parameter. +* [@dustyhoppe](https://github.com/dustyhoppe) - Overloads where onTimeout takes thrown exception as a parameter # Sample Projects diff --git a/src/Polly.nuspec b/src/Polly.nuspec index f43b562d585..a078e9659a7 100644 --- a/src/Polly.nuspec +++ b/src/Polly.nuspec @@ -15,7 +15,10 @@ 6.1.0 --------------------- - - Improved cache error message (issue 455) + - Bug Fix: Context.PolicyKey behaviour in PolicyWrap + - Bug Fix: CachePolicy behaviour with non-nullable types + - Enhancement: WaitAnd/RetryForever overloads where onRetry takes the retry number as a parameter + - Enhancement: Overloads where onTimeout takes thrown exception as a parameter 6.0.1 ---------------------