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

Simplify handling of reloads #1374

Merged
merged 3 commits into from
Jun 30, 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
17 changes: 10 additions & 7 deletions src/Polly.Core/Registry/ConfigureBuilderContext.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.ComponentModel;

namespace Polly.Registry;

/// <summary>
Expand Down Expand Up @@ -29,20 +31,21 @@ internal ConfigureBuilderContext(TKey strategyKey, string builderName, string st
/// </summary>
public string StrategyKeyString { get; }

internal Func<CancellationToken>? ReloadTokenProducer { get; private set; }
internal Func<Func<CancellationToken>>? ReloadTokenProducer { get; private set; }

/// <summary>
/// Enables dynamic reloading of the strategy retrieved from <see cref="ResilienceStrategyRegistry{TKey}"/>.
/// </summary>
/// <param name="reloadTokenProducer">The producer of <see cref="CancellationToken"/> that is triggered when change occurs.</param>
/// <param name="tokenProducerFactory">The producer of <see cref="CancellationToken"/> that is triggered when change occurs.</param>
/// <remarks>
/// The <paramref name="reloadTokenProducer"/> should always return a new <see cref="CancellationToken"/> instance when invoked otherwise
/// the reload infrastructure will stop listening for changes.
/// The <paramref name="tokenProducerFactory"/> should always return function that returns a new <see cref="CancellationToken"/> instance when invoked otherwise
/// the reload infrastructure will stop listening for changes. The <paramref name="tokenProducerFactory"/> is called only once for each streategy.
/// </remarks>
public void EnableReloads(Func<CancellationToken> reloadTokenProducer)
[EditorBrowsable(EditorBrowsableState.Never)]
public void EnableReloads(Func<Func<CancellationToken>> tokenProducerFactory)
{
Guard.NotNull(reloadTokenProducer);
Guard.NotNull(tokenProducerFactory);

ReloadTokenProducer = reloadTokenProducer;
ReloadTokenProducer = tokenProducerFactory;
}
}
2 changes: 1 addition & 1 deletion src/Polly.Core/Registry/ResilienceStrategyRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ private static ResilienceStrategy CreateStrategy<TBuilder>(

return new ReloadableResilienceStrategy(
strategy,
context.ReloadTokenProducer,
context.ReloadTokenProducer(),
() => factory().BuildStrategy(),
TelemetryUtil.CreateTelemetry(
diagnosticSource,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Polly.Extensions.Utils;
Expand All @@ -17,8 +18,14 @@ internal AddResilienceStrategyContext(ConfigureBuilderContext<TKey> registryCont
RegistryContext = registryContext;
ServiceProvider = serviceProvider;
StrategyKey = registryContext.StrategyKey;
BuilderName = registryContext.BuilderName;
}

/// <summary>
/// Gets the strategy key for the strategy being created.
/// </summary>
public string BuilderName { get; }

/// <summary>
/// Gets the strategy key for the strategy being created.
/// </summary>
Expand All @@ -32,7 +39,7 @@ internal AddResilienceStrategyContext(ConfigureBuilderContext<TKey> registryCont
/// <summary>
/// Gets the context that is used by the registry.
/// </summary>
public ConfigureBuilderContext<TKey> RegistryContext { get; }
internal ConfigureBuilderContext<TKey> RegistryContext { get; }

/// <summary>
/// Enables dynamic reloading of the resilience strategy whenever the <typeparamref name="TOptions"/> options are changed.
Expand All @@ -48,10 +55,9 @@ internal AddResilienceStrategyContext(ConfigureBuilderContext<TKey> registryCont
/// </remarks>
public void EnableReloads<TOptions>(string? name = null)
{
var registry = ServiceProvider.GetRequiredService<OptionsReloadHelperRegistry<TKey>>();
var helper = registry.Get<TOptions>(StrategyKey, name);
var monitor = ServiceProvider.GetRequiredService<IOptionsMonitor<TOptions>>();

RegistryContext.EnableReloads(helper.GetCancellationToken);
RegistryContext.EnableReloads(() => new OptionsReloadHelper<TOptions>(monitor, name ?? Options.DefaultName).GetCancellationToken);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.Extensions.Options;
using Polly.Extensions.DependencyInjection;
using Polly.Extensions.Telemetry;
using Polly.Extensions.Utils;
using Polly.Registry;
using Polly.Utils;

Expand Down Expand Up @@ -174,7 +173,6 @@ private static IServiceCollection AddResilienceStrategyRegistry<TKey>(this IServ
services.Add(RegistryMarker<TKey>.ServiceDescriptor);
services.AddResilienceStrategyBuilder();
services.AddResilienceStrategyRegistry<TKey>();
services.TryAddSingleton<OptionsReloadHelperRegistry<TKey>>();

services.TryAddSingleton(serviceProvider =>
{
Expand Down
14 changes: 5 additions & 9 deletions src/Polly.Extensions/Utils/OptionsReloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

namespace Polly.Extensions.Utils;

internal sealed class OptionsReloadHelper<T> : IDisposable
#pragma warning disable CA1001 // we can get away of not disposing this class because it's active for a lifetime of app
#pragma warning disable S2931

internal sealed class OptionsReloadHelper<T>
{
private readonly IDisposable? _listener;
private CancellationTokenSource _cancellation = new();

public OptionsReloadHelper(IOptionsMonitor<T> monitor, string name) => _listener = monitor.OnChange((_, changedNamed) =>
public OptionsReloadHelper(IOptionsMonitor<T> monitor, string name) => monitor.OnChange((_, changedNamed) =>
{
if (name == changedNamed)
{
Expand All @@ -17,12 +19,6 @@ public OptionsReloadHelper(IOptionsMonitor<T> monitor, string name) => _listener

public CancellationToken GetCancellationToken() => _cancellation.Token;

public void Dispose()
{
_cancellation.Dispose();
_listener?.Dispose();
}

private void HandleChange()
{
var oldCancellation = _cancellation;
Expand Down
34 changes: 0 additions & 34 deletions src/Polly.Extensions/Utils/OptionsReloadHelperRegistry.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ public void EnableReloads_Ok()
registry.TryAddBuilder("dummy", (builder, context) =>
{
// this call enables dynamic reloads for the dummy strategy
context.EnableReloads(() => changeSource.Token);
context.EnableReloads(() => () => changeSource.Token);

builder.AddRetry(new RetryStrategyOptions
{
Expand Down Expand Up @@ -404,7 +404,7 @@ public void EnableReloads_Generic_Ok()
registry.TryAddBuilder<string>("dummy", (builder, context) =>
{
// this call enables dynamic reloads for the dummy strategy
context.EnableReloads(() => changeSource.Token);
context.EnableReloads(() => () => changeSource.Token);

builder.AddRetry(new RetryStrategyOptions<string>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public void AddResilienceStrategy_EnsureContextFilled(bool generic)
{
context.RegistryContext.Should().NotBeNull();
context.StrategyKey.Should().Be(Key);
context.BuilderName.Should().Be(Key);
builder.Should().NotBeNull();
context.ServiceProvider.Should().NotBeNull();
builder.AddStrategy(new TestStrategy());
Expand Down
36 changes: 0 additions & 36 deletions test/Polly.Extensions.Tests/ReloadableResilienceStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Polly.Extensions.Utils;
using Polly.Registry;

namespace Polly.Extensions.Tests;
Expand Down Expand Up @@ -43,7 +40,6 @@ public void AddResilienceStrategy_EnsureReloadable(string? name)
var serviceProvider = services.BuildServiceProvider();
var strategy = serviceProvider.GetRequiredService<ResilienceStrategyProvider<string>>().GetStrategy("my-strategy");
var context = ResilienceContext.Get();
var registry = serviceProvider.GetRequiredService<OptionsReloadHelperRegistry<string>>();

// initial
strategy.Execute(_ => "dummy", context);
Expand All @@ -56,38 +52,6 @@ public void AddResilienceStrategy_EnsureReloadable(string? name)
strategy.Execute(_ => "dummy", context);
context.Properties.GetValue(TagKey, string.Empty).Should().Be($"reload-{i}");
}

registry.Count.Should().Be(1);
serviceProvider.Dispose();
registry.Count.Should().Be(0);
}

[Fact]
public void OptionsReloadHelperRegistry_EnsureTracksDifferentHelpers()
{
var services = new ServiceCollection().AddResilienceStrategy("dummy", builder => { });
var serviceProvider = services.BuildServiceProvider();
var registry = serviceProvider.GetRequiredService<OptionsReloadHelperRegistry<string>>();

registry.Get<ReloadableStrategy>("A", null);
registry.Get<ReloadableStrategy>("A", "dummy");
registry.Get<ReloadableStrategy>("B", null);
registry.Get<ReloadableStrategy>("B", "dummy");

registry.Count.Should().Be(4);

registry.Dispose();
registry.Count.Should().Be(0);
}

[Fact]
public void OptionsReloadHelper_Dispose_Ok()
{
var monitor = new Mock<IOptionsMonitor<ReloadableStrategyOptions>>();

using var helper = new OptionsReloadHelper<ReloadableStrategyOptions>(monitor.Object, string.Empty);

helper.Invoking(h => h.Dispose()).Should().NotThrow();
}

public class ReloadableStrategy : ResilienceStrategy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void Ctor_NamedOptions()
.Setup(m => m.OnChange(It.IsAny<Action<string, string?>>()))
.Returns(() => Mock.Of<IDisposable>());

using var helper = new OptionsReloadHelper<string>(monitor.Object, "name");
var helper = new OptionsReloadHelper<string>(monitor.Object, "name");

monitor.VerifyAll();
}
Expand Down