Skip to content

Commit 3f76b98

Browse files
authored
Fix non-generic Fallback with generic Execute method (#372)
Closes #294.
1 parent dfa8fc9 commit 3f76b98

11 files changed

Lines changed: 128 additions & 15 deletions

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
## 5.6.0
22
- Add ability to handle inner exceptions natively: .HandleInner<TEx>()
33
- Allow WaitAndRetry policies to calculate wait based on the handled fault
4-
- Add GetPolicies() extension methods to IPolicyWrap
5-
- Allow PolicyWrap to take interfaces as parameters
6-
- Bug fix: set context keys for generic execute method with PolicyWrap
4+
- Add the ability to access the policies within an IPolicyWrap
5+
- Allow PolicyWrap to configure policies expressed as interfaces
6+
- Bug fix: set context keys for generic execute methods with PolicyWrap
7+
- Bug fix: generic TResult method with non-generic fallback policy
78
- Performance improvements
89
- Multiple build speed improvements
910

src/Polly.Net40Async.nuspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
---------------------
2020
- Add ability to handle inner exceptions natively: .HandleInner<TEx>()
2121
- Allow WaitAndRetry policies to calculate wait based on the handled fault
22-
- Add GetPolicies() extension methods to IPolicyWrap
22+
- Add the ability to access the policies within an IPolicyWrap
2323
- Allow PolicyWrap to take interfaces as parameters
2424
- Bug fix: set context keys for generic execute method with PolicyWrap
25+
- Bug fix: generic TResult method with non-generic fallback policy
2526
- Performance improvements
2627

2728
5.5.0

src/Polly.Shared/Fallback/FallbackPolicy.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ internal FallbackPolicy(Action<Action<Context, CancellationToken>, Context, Canc
1313
: base(exceptionPolicy, exceptionPredicates)
1414
{
1515
}
16+
17+
/// <summary>
18+
/// Executes the specified action within the cache policy and returns the result.
19+
/// </summary>
20+
/// <typeparam name="TResult">The type of the result.</typeparam>
21+
/// <param name="action">The action to perform.</param>
22+
/// <param name="context">Execution context that is passed to the exception policy; defines the cache key to use in cache lookup.</param>
23+
/// <param name="cancellationToken">The cancellation token.</param>
24+
/// <returns>The value returned by the action, or the cache.</returns>
25+
public override TResult ExecuteInternal<TResult>(Func<Context, CancellationToken, TResult> action, Context context, CancellationToken cancellationToken)
26+
{
27+
throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle<Whatever>.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);");
28+
}
1629
}
1730

1831
/// <summary>

src/Polly.Shared/Fallback/FallbackPolicyAsync.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Threading;
45
using System.Threading.Tasks;
56

@@ -11,6 +12,22 @@ internal FallbackPolicy(Func<Func<Context, CancellationToken, Task>, Context, Ca
1112
: base(asyncExceptionPolicy, exceptionPredicates)
1213
{
1314
}
15+
16+
/// <summary>
17+
/// Executes the specified asynchronous action within the policy and returns the result.
18+
/// </summary>
19+
/// <typeparam name="TResult">The type of the result.</typeparam>
20+
/// <param name="action">The action to perform.</param>
21+
/// <param name="context">Context data that is passed to the exception policy.</param>
22+
/// <param name="continueOnCapturedContext">Whether to continue on a captured synchronization context.</param>
23+
/// <param name="cancellationToken">A cancellation token which can be used to cancel the action. When a retry policy is in use, also cancels any further retries.</param>
24+
/// <returns>The value returned by the action</returns>
25+
/// <exception cref="System.InvalidOperationException">Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods.</exception>
26+
[DebuggerStepThrough]
27+
public override Task<TResult> ExecuteAsyncInternal<TResult>(Func<Context, CancellationToken, Task<TResult>> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext)
28+
{
29+
throw new InvalidOperationException($"You have executed the generic .Execute<{nameof(TResult)}> method on a non-generic {nameof(FallbackPolicy)}. A non-generic {nameof(FallbackPolicy)} only defines a fallback action which returns void; it can never return a substitute {nameof(TResult)} value. To use {nameof(FallbackPolicy)} to provide fallback {nameof(TResult)} values you must define a generic fallback policy {nameof(FallbackPolicy)}<{nameof(TResult)}>. For example, define the policy as Policy<{nameof(TResult)}>.Handle<Whatever>.Fallback<{nameof(TResult)}>(/* some {nameof(TResult)} value or Func<..., {nameof(TResult)}> */);");
30+
}
1431
}
1532

1633
public partial class FallbackPolicy<TResult> : IFallbackPolicy<TResult>

src/Polly.SharedSpecs/Fallback/FallbackAsyncSpecs.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,16 @@ public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_e
251251
fallbackActionExecuted.Should().BeTrue();
252252
}
253253

254+
[Fact]
255+
public void Should_throw_for_generic_method_execution_on_non_generic_policy()
256+
{
257+
FallbackPolicy fallbackPolicy = Policy
258+
.Handle<DivideByZeroException>()
259+
.FallbackAsync(_ => TaskHelper.EmptyTask);
260+
261+
fallbackPolicy.Awaiting(p => p.ExecuteAsync<int>(() => TaskHelper.FromResult(0))).ShouldThrow<InvalidOperationException>();
262+
}
263+
254264
#endregion
255265

256266
#region onPolicyEvent delegate tests

src/Polly.SharedSpecs/Fallback/FallbackSpecs.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,16 @@ public void Should_not_handle_exception_thrown_by_fallback_delegate_even_if_is_e
316316
fallbackActionExecuted.Should().BeTrue();
317317
}
318318

319+
[Fact]
320+
public void Should_throw_for_generic_method_execution_on_non_generic_policy()
321+
{
322+
FallbackPolicy fallbackPolicy = Policy
323+
.Handle<DivideByZeroException>()
324+
.Fallback(() => {});
325+
326+
fallbackPolicy.Invoking(p => p.Execute<int>(() => 0)).ShouldThrow<InvalidOperationException>();
327+
}
328+
319329
#endregion
320330

321331
#region HandleInner tests, inner of normal exceptions

src/Polly.SharedSpecs/Helpers/PolicyExtensions.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static void RaiseException<TException>(this Policy policy, TException ins
2222
NumberOfTimesToRaiseException = 1
2323
};
2424

25-
policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, _ => instance, 0);
25+
policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, _ => instance);
2626
}
2727

2828
public static void RaiseException<TException>(this Policy policy, Action<TException, int> configureException = null) where TException : Exception, new()
@@ -46,12 +46,12 @@ public static void RaiseException<TException>(this Policy policy, TException ins
4646
return exception;
4747
};
4848

49-
policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, exceptionFactory, 0);
49+
policy.RaiseExceptionAndOrCancellation(scenario, new CancellationTokenSource(), () => { }, exceptionFactory);
5050
}
5151

5252
public static void RaiseExceptionAndOrCancellation<TException>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new()
5353
{
54-
policy.RaiseExceptionAndOrCancellation<TException, int>(scenario, cancellationTokenSource, onExecute, 0);
54+
policy.RaiseExceptionAndOrCancellation<TException>(scenario, cancellationTokenSource, onExecute, _ => new TException());
5555
}
5656

5757
public static TResult RaiseExceptionAndOrCancellation<TException, TResult>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new()
@@ -60,6 +60,36 @@ public static void RaiseException<TException>(this Policy policy, TException ins
6060
_ => new TException(), successResult);
6161
}
6262

63+
public static void RaiseExceptionAndOrCancellation<TException>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func<int, TException> exceptionFactory) where TException : Exception
64+
{
65+
int counter = 0;
66+
67+
CancellationToken cancellationToken = cancellationTokenSource.Token;
68+
69+
policy.Execute(ct =>
70+
{
71+
onExecute();
72+
73+
counter++;
74+
75+
if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value)
76+
{
77+
cancellationTokenSource.Cancel();
78+
}
79+
80+
if (scenario.ActionObservesCancellation)
81+
{
82+
ct.ThrowIfCancellationRequested();
83+
}
84+
85+
if (counter <= scenario.NumberOfTimesToRaiseException)
86+
{
87+
throw exceptionFactory(counter);
88+
}
89+
90+
}, cancellationToken);
91+
}
92+
6393
public static TResult RaiseExceptionAndOrCancellation<TException, TResult>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func<int, TException> exceptionFactory, TResult successResult) where TException : Exception
6494
{
6595
int counter = 0;

src/Polly.SharedSpecs/Helpers/PolicyExtensionsAsync.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static Task RaiseExceptionAsync<TException>(this Policy policy, TExceptio
2525
NumberOfTimesToRaiseException = 1
2626
};
2727

28-
return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance, 0);
28+
return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, _ => instance);
2929
}
3030

3131
public static Task RaiseExceptionAsync<TException>(this Policy policy, Action<TException, int> configureException = null) where TException : Exception, new()
@@ -49,12 +49,12 @@ public static Task RaiseExceptionAsync<TException>(this Policy policy, TExceptio
4949
return exception;
5050
};
5151

52-
return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, exceptionFactory, 0);
52+
return policy.RaiseExceptionAndOrCancellationAsync(scenario, new CancellationTokenSource(), () => { }, exceptionFactory);
5353
}
5454

5555
public static Task RaiseExceptionAndOrCancellationAsync<TException>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute) where TException : Exception, new()
5656
{
57-
return policy.RaiseExceptionAndOrCancellationAsync<TException, int>(scenario, cancellationTokenSource, onExecute, 0);
57+
return policy.RaiseExceptionAndOrCancellationAsync<TException>(scenario, cancellationTokenSource, onExecute, _ => new TException());
5858
}
5959

6060
public static Task<TResult> RaiseExceptionAndOrCancellationAsync<TException, TResult>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, TResult successResult) where TException : Exception, new()
@@ -63,6 +63,37 @@ public static Task RaiseExceptionAsync<TException>(this Policy policy, TExceptio
6363
_ => new TException(), successResult);
6464
}
6565

66+
public static Task RaiseExceptionAndOrCancellationAsync<TException>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func<int, TException> exceptionFactory) where TException : Exception
67+
{
68+
int counter = 0;
69+
70+
CancellationToken cancellationToken = cancellationTokenSource.Token;
71+
72+
return policy.ExecuteAsync(ct =>
73+
{
74+
onExecute();
75+
76+
counter++;
77+
78+
if (scenario.AttemptDuringWhichToCancel.HasValue && counter >= scenario.AttemptDuringWhichToCancel.Value)
79+
{
80+
cancellationTokenSource.Cancel();
81+
}
82+
83+
if (scenario.ActionObservesCancellation)
84+
{
85+
ct.ThrowIfCancellationRequested();
86+
}
87+
88+
if (counter <= scenario.NumberOfTimesToRaiseException)
89+
{
90+
throw exceptionFactory(counter);
91+
}
92+
93+
return TaskHelper.EmptyTask;
94+
}, cancellationToken);
95+
}
96+
6697
public static Task<TResult> RaiseExceptionAndOrCancellationAsync<TException, TResult>(this Policy policy, ExceptionAndOrCancellationScenario scenario, CancellationTokenSource cancellationTokenSource, Action onExecute, Func<int, TException> exceptionFactory, TResult successResult) where TException : Exception
6798
{
6899
int counter = 0;

src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecs.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,13 @@ public void Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermost_Pol
115115
var outerWrap = fallback.Wrap(innerWrap).WithPolicyKey(outerWrapKey);
116116

117117
bool doneOnceOny = false;
118-
outerWrap.Execute<int>(() =>
118+
outerWrap.Execute(() =>
119119
{
120120
if (!doneOnceOny)
121121
{
122122
doneOnceOny = true;
123123
throw new Exception();
124124
}
125-
return 0;
126125
});
127126

128127
policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey);

src/Polly.SharedSpecs/Wrap/PolicyWrapContextAndKeySpecsAsync.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ public async Task Should_pass_outmost_PolicyWrap_Key_as_PolicyWrapKey_to_innermo
117117
var outerWrap = fallback.WrapAsync(innerWrap).WithPolicyKey(outerWrapKey);
118118

119119
bool doneOnceOny = false;
120-
await outerWrap.ExecuteAsync<int>(() =>
120+
await outerWrap.ExecuteAsync(() =>
121121
{
122122
if (!doneOnceOny)
123123
{
124124
doneOnceOny = true;
125125
throw new Exception();
126126
}
127-
return TaskHelper.FromResult(0);
127+
return TaskHelper.EmptyTask;
128128
});
129129

130130
policyWrapKeySetOnExecutionContext.Should().NotBe(retryKey);

0 commit comments

Comments
 (0)