Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use invokers in fallback and hedging #1232

Merged
merged 1 commit into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ LaunchCount=2 WarmupCount=10
```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---------------------------- |---------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Hedging_Primary | 1.057 μs | 0.0027 μs | 0.0040 μs | 1.00 | 0.00 | 0.0019 | - | 80 B | 1.00 |
| Hedging_Secondary | 1.773 μs | 0.0130 μs | 0.0194 μs | 1.68 | 0.02 | 0.0095 | - | 280 B | 3.50 |
| Hedging_Primary_AsyncWork | 5.112 μs | 0.1458 μs | 0.2138 μs | 4.84 | 0.21 | 0.0534 | 0.0229 | 1506 B | 18.82 |
| Hedging_Secondary_AsyncWork | 7.910 μs | 0.1827 μs | 0.2501 μs | 7.48 | 0.25 | 0.0763 | 0.0381 | 1999 B | 24.99 |
| Hedging_Primary | 1.049 μs | 0.0074 μs | 0.0108 μs | 1.00 | 0.00 | - | - | 40 B | 1.00 |
| Hedging_Secondary | 1.788 μs | 0.0093 μs | 0.0139 μs | 1.71 | 0.02 | 0.0076 | - | 200 B | 5.00 |
| Hedging_Primary_AsyncWork | 5.306 μs | 0.1484 μs | 0.2222 μs | 5.07 | 0.24 | 0.0534 | 0.0229 | 1444 B | 36.10 |
| Hedging_Secondary_AsyncWork | 7.518 μs | 0.0869 μs | 0.1246 μs | 7.17 | 0.12 | 0.0610 | 0.0534 | 1684 B | 42.10 |
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ LaunchCount=2 WarmupCount=10
```
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|--------------------------- |---------:|----------:|----------:|------:|-------:|----------:|------------:|
| ExecuteStrategyPipeline_V7 | 2.178 μs | 0.0167 μs | 0.0249 μs | 1.00 | 0.1106 | 2824 B | 1.00 |
| ExecuteStrategyPipeline_V8 | 1.687 μs | 0.0028 μs | 0.0041 μs | 0.77 | 0.0019 | 72 B | 0.03 |
| ExecuteStrategyPipeline_V7 | 2.269 μs | 0.0136 μs | 0.0195 μs | 1.00 | 0.1106 | 2824 B | 1.00 |
| ExecuteStrategyPipeline_V8 | 1.640 μs | 0.0059 μs | 0.0088 μs | 0.72 | 0.0019 | 72 B | 0.03 |
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,8 @@ public void Handle_UnhandledResult_Ok()
fallbackActionCalled.Should().BeFalse();
}

private FallbackResilienceStrategy Create() => new(_options, _telemetry);
private FallbackResilienceStrategy Create() => new(
_options.Handler.CreateHandler(),
EventInvoker<OnFallbackArguments>.NonGeneric(_options.OnFallback),
_telemetry);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Polly.Fallback;
using Polly.Strategy;
using Polly.Utils;

namespace Polly.Core.Tests.Fallback;
Expand Down Expand Up @@ -38,40 +37,4 @@ The ShouldHandle field is required.
The FallbackAction field is required.
""");
}

[Fact]
public async Task AsNonGenericOptions_Ok()
{
var called = false;
var options = new FallbackStrategyOptions<int>
{
OnFallback = (_, _) => { called = true; return default; },
ShouldHandle = (outcome, _) => new ValueTask<bool>(outcome.Result == -1),
FallbackAction = (_, _) => new ValueTask<int>(1),
StrategyName = "Dummy",
};

var nonGeneric = options.AsNonGenericOptions();

nonGeneric.Should().NotBeNull();
nonGeneric.StrategyType.Should().Be("Fallback");
nonGeneric.StrategyName.Should().Be("Dummy");
nonGeneric.Handler.IsEmpty.Should().BeFalse();

var handler = nonGeneric.Handler.CreateHandler();
handler.Should().NotBeNull();

var result = await handler!.ShouldHandleAsync(new Outcome<int>(-1), new HandleFallbackArguments(ResilienceContext.Get()));
result.Should().Be(options.FallbackAction);

result = await handler!.ShouldHandleAsync(new Outcome<int>(0), new HandleFallbackArguments(ResilienceContext.Get()));
result.Should().BeNull();

nonGeneric.OnFallback.Should().NotBeNull();
await nonGeneric.OnFallback!(new Outcome(0), new OnFallbackArguments(ResilienceContext.Get()));
called.Should().BeFalse();

await nonGeneric.OnFallback!(new Outcome(0), new OnFallbackArguments(ResilienceContext.Get().Initialize<int>(true)));
called.Should().BeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -884,5 +884,12 @@ private void ConfigureHedging(TimeSpan delay) => ConfigureHedging(args => async
return "secondary";
});

private HedgingResilienceStrategy Create() => new(_options, _timeProvider, _telemetry);
private HedgingResilienceStrategy Create() => new(
_options.HedgingDelay,
_options.MaxHedgedAttempts,
_options.Handler.CreateHandler(),
EventInvoker<OnHedgingArguments>.NonGeneric(_options.OnHedging),
_options.HedgingDelayGenerator,
_timeProvider,
_telemetry);
}
52 changes: 0 additions & 52 deletions src/Polly.Core.Tests/Hedging/HedgingStrategyOptionsTResultTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Polly.Hedging;
using Polly.Strategy;
using Polly.Utils;

namespace Polly.Core.Tests.Hedging;
Expand Down Expand Up @@ -44,55 +43,4 @@ The ShouldHandle field is required.
The HedgingActionGenerator field is required.
""");
}

[Fact]
public async Task AsNonGenericOptions_Ok()
{
var onHedgingCalled = false;
var options = new HedgingStrategyOptions<int>
{
HedgingDelayGenerator = args => new ValueTask<TimeSpan>(TimeSpan.FromSeconds(123)),
ShouldHandle = (outcome, _) => new ValueTask<bool>(outcome.Result == -1),
StrategyName = "Dummy",
HedgingDelay = TimeSpan.FromSeconds(3),
MaxHedgedAttempts = 4,
HedgingActionGenerator = args => () => Task.FromResult(555),
OnHedging = (_, args) =>
{
args.Context.Should().NotBeNull();
args.Attempt.Should().Be(3);
onHedgingCalled = true;
return default;
}
};

var nonGeneric = options.AsNonGenericOptions();

nonGeneric.Should().NotBeNull();
nonGeneric.StrategyType.Should().Be("Hedging");
nonGeneric.StrategyName.Should().Be("Dummy");
nonGeneric.Handler.IsEmpty.Should().BeFalse();
nonGeneric.MaxHedgedAttempts.Should().Be(4);
nonGeneric.HedgingDelay.Should().Be(TimeSpan.FromSeconds(3));

var handler = nonGeneric.Handler.CreateHandler();
handler.Should().NotBeNull();

(await handler!.TryCreateHedgedAction<int>(ResilienceContext.Get(), 0)!()).Should().Be(555);

var result = await handler!.ShouldHandleAsync(new Outcome<int>(-1), new HandleHedgingArguments(ResilienceContext.Get()));
result.Should().BeTrue();

result = await handler!.ShouldHandleAsync(new Outcome<int>(0), new HandleHedgingArguments(ResilienceContext.Get()));
result.Should().BeFalse();

var delay = await nonGeneric.HedgingDelayGenerator!(new HedgingDelayArguments(ResilienceContext.Get(), 4));
delay.Should().Be(TimeSpan.FromSeconds(123));

await nonGeneric.OnHedging!(new Outcome(10), new OnHedgingArguments(ResilienceContext.Get(), 3));
onHedgingCalled.Should().BeFalse();

await nonGeneric.OnHedging!(new Outcome(10), new OnHedgingArguments(ResilienceContext.Get().Initialize<int>(true), 3));
onHedgingCalled.Should().BeTrue();
}
}
10 changes: 5 additions & 5 deletions src/Polly.Core/Fallback/FallbackResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ namespace Polly.Fallback;
internal sealed class FallbackResilienceStrategy : ResilienceStrategy
{
private readonly FallbackHandler.Handler _handler;
private readonly Func<Outcome, OnFallbackArguments, ValueTask>? _onFallback;
private readonly EventInvoker<OnFallbackArguments>? _onFallback;
private readonly ResilienceStrategyTelemetry _telemetry;

public FallbackResilienceStrategy(FallbackStrategyOptions options, ResilienceStrategyTelemetry telemetry)
public FallbackResilienceStrategy(FallbackHandler.Handler handler, EventInvoker<OnFallbackArguments>? onFallback, ResilienceStrategyTelemetry telemetry)
{
_handler = options.Handler.CreateHandler();
_onFallback = options.OnFallback;
_handler = handler;
_onFallback = onFallback;
_telemetry = telemetry;
}

Expand All @@ -38,7 +38,7 @@ protected internal override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<T

if (_onFallback is not null)
{
await _onFallback!(outcome.AsOutcome(), new OnFallbackArguments(context)).ConfigureAwait(context.ContinueOnCapturedContext);
await _onFallback.HandleAsync(outcome, new OnFallbackArguments(context)).ConfigureAwait(context.ContinueOnCapturedContext);
}

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,20 @@ public static ResilienceStrategyBuilder<TResult> AddFallback<TResult>(this Resil

ValidationHelper.ValidateObject(options, "The fallback strategy options are invalid.");

return builder.AddStrategy(context => new FallbackResilienceStrategy(options.AsNonGenericOptions(), context.Telemetry), options);
var handler = new FallbackHandler()
.SetFallback<TResult>(handler =>
{
handler.FallbackAction = options.FallbackAction;
handler.ShouldHandle = options.ShouldHandle;
})
.CreateHandler();

return builder.AddStrategy(context =>
new FallbackResilienceStrategy(
handler,
EventInvoker<OnFallbackArguments>.Generic(options.OnFallback),
context.Telemetry),
options);
}

/// <summary>
Expand All @@ -75,6 +88,11 @@ internal static ResilienceStrategyBuilder AddFallback(this ResilienceStrategyBui

ValidationHelper.ValidateObject(options, "The fallback strategy options are invalid.");

return builder.AddStrategy(context => new FallbackResilienceStrategy(options, context.Telemetry), options);
return builder.AddStrategy(context =>
new FallbackResilienceStrategy(
options.Handler.CreateHandler(),
EventInvoker<OnFallbackArguments>.NonGeneric(options.OnFallback),
context.Telemetry),
options);
}
}
28 changes: 0 additions & 28 deletions src/Polly.Core/Fallback/FallbackStrategyOptions.TResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,33 +40,5 @@ public class FallbackStrategyOptions<TResult> : ResilienceStrategyOptions
/// Defaults to <see langword="null"/> instance.
/// </remarks>
public Func<Outcome<TResult>, OnFallbackArguments, ValueTask>? OnFallback { get; set; }

internal FallbackStrategyOptions AsNonGenericOptions()
{
var options = new FallbackStrategyOptions
{
StrategyName = StrategyName,
Handler = new FallbackHandler().SetFallback<TResult>(handler =>
{
handler.ShouldHandle = ShouldHandle;
handler.FallbackAction = FallbackAction;
})
};

if (OnFallback is { } fallback)
{
options.OnFallback = (outcome, args) =>
{
if (args.Context.ResultType != typeof(TResult))
{
return default;
}

return fallback!(outcome.AsOutcome<TResult>(), args);
};
}

return options;
}
}

25 changes: 16 additions & 9 deletions src/Polly.Core/Hedging/HedgingResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,23 @@ internal sealed class HedgingResilienceStrategy : ResilienceStrategy
private readonly ResilienceStrategyTelemetry _telemetry;
private readonly HedgingController _controller;

public HedgingResilienceStrategy(HedgingStrategyOptions options, TimeProvider timeProvider, ResilienceStrategyTelemetry telemetry)
public HedgingResilienceStrategy(
TimeSpan hedgingDelay,
int maxHedgedAttempts,
HedgingHandler.Handler hedgingHandler,
EventInvoker<OnHedgingArguments>? onHedging,
Func<HedgingDelayArguments, ValueTask<TimeSpan>>? hedgingDelayGenerator,
TimeProvider timeProvider,
ResilienceStrategyTelemetry telemetry)
{
HedgingDelay = options.HedgingDelay;
MaxHedgedAttempts = options.MaxHedgedAttempts;
HedgingDelayGenerator = options.HedgingDelayGenerator;
HedgingHandler = options.Handler.CreateHandler();
OnHedging = options.OnHedging;
HedgingDelay = hedgingDelay;
MaxHedgedAttempts = maxHedgedAttempts;
HedgingDelayGenerator = hedgingDelayGenerator;
HedgingHandler = hedgingHandler;
OnHedging = onHedging;

_telemetry = telemetry;
_controller = new HedgingController(timeProvider, HedgingHandler, options.MaxHedgedAttempts);
_controller = new HedgingController(timeProvider, HedgingHandler, maxHedgedAttempts);
}

public TimeSpan HedgingDelay { get; }
Expand All @@ -32,7 +39,7 @@ public HedgingResilienceStrategy(HedgingStrategyOptions options, TimeProvider ti

public HedgingHandler.Handler HedgingHandler { get; }

public Func<Outcome, OnHedgingArguments, ValueTask>? OnHedging { get; }
public EventInvoker<OnHedgingArguments>? OnHedging { get; }

protected internal override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
Expand Down Expand Up @@ -87,7 +94,7 @@ protected internal override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<T
// If nothing has been returned or thrown yet, the result is a transient failure,
// and other hedged request will be awaited.
// Before it, one needs to perform the task adjacent to each hedged call.
await OnHedging(outcome.AsOutcome(), onHedgingArgs).ConfigureAwait(continueOnCapturedContext);
await OnHedging.HandleAsync(outcome, onHedgingArgs).ConfigureAwait(continueOnCapturedContext);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Polly.Hedging;
using Polly.Strategy;

namespace Polly;

Expand All @@ -24,7 +25,24 @@ public static ResilienceStrategyBuilder<TResult> AddHedging<TResult>(this Resili

ValidationHelper.ValidateObject(options, "The hedging strategy options are invalid.");

return builder.AddStrategy(context => new HedgingResilienceStrategy(options.AsNonGenericOptions(), context.TimeProvider, context.Telemetry), options);
var handler = new HedgingHandler()
.SetHedging<TResult>(handler =>
{
handler.HedgingActionGenerator = options.HedgingActionGenerator;
handler.ShouldHandle = options.ShouldHandle;
})
.CreateHandler();

return builder.AddStrategy(context =>
new HedgingResilienceStrategy(
options.HedgingDelay,
options.MaxHedgedAttempts,
handler,
EventInvoker<OnHedgingArguments>.Generic(options.OnHedging),
options.HedgingDelayGenerator,
context.TimeProvider,
context.Telemetry),
options);
}

/// <summary>
Expand All @@ -42,6 +60,15 @@ internal static ResilienceStrategyBuilder AddHedging(this ResilienceStrategyBuil

ValidationHelper.ValidateObject(options, "The hedging strategy options are invalid.");

return builder.AddStrategy(context => new HedgingResilienceStrategy(options, context.TimeProvider, context.Telemetry), options);
return builder.AddStrategy(context =>
new HedgingResilienceStrategy(
options.HedgingDelay,
options.MaxHedgedAttempts,
options.Handler.CreateHandler(),
EventInvoker<OnHedgingArguments>.NonGeneric(options.OnHedging),
options.HedgingDelayGenerator,
context.TimeProvider,
context.Telemetry),
options);
}
}
31 changes: 0 additions & 31 deletions src/Polly.Core/Hedging/HedgingStrategyOptions.TResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,35 +76,4 @@ public class HedgingStrategyOptions<TResult> : ResilienceStrategyOptions
/// Defaults to <see langword="null"/>.
/// </remarks>
public Func<Outcome<TResult>, OnHedgingArguments, ValueTask>? OnHedging { get; set; }

internal HedgingStrategyOptions AsNonGenericOptions()
{
var options = new HedgingStrategyOptions
{
StrategyName = StrategyName,
HedgingDelay = HedgingDelay,
Handler = new HedgingHandler().SetHedging<TResult>(handler =>
{
handler.ShouldHandle = ShouldHandle;
handler.HedgingActionGenerator = HedgingActionGenerator;
}),
MaxHedgedAttempts = MaxHedgedAttempts,
HedgingDelayGenerator = HedgingDelayGenerator
};

if (OnHedging is { } onHedging)
{
options.OnHedging = (outcome, args) =>
{
if (args.Context.ResultType != typeof(TResult))
{
return default;
}

return onHedging!(outcome.AsOutcome<TResult>(), args);
};
}

return options;
}
}