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

ResilienceStrategyRegistry now uses context to configure the builder #1242

Merged
merged 1 commit into from
May 31, 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
30 changes: 16 additions & 14 deletions src/Polly.Core.Tests/Registry/ResilienceStrategyRegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Clear_Ok()
{
var registry = new ResilienceStrategyRegistry<string>();

registry.TryAddBuilder("C", (_, b) => b.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder("C", (b, _) => b.AddStrategy(new TestResilienceStrategy()));

registry.TryAdd("A", new TestResilienceStrategy());
registry.TryAdd("B", new TestResilienceStrategy());
Expand All @@ -60,7 +60,7 @@ public void Clear_Generic_Ok()
{
var registry = new ResilienceStrategyRegistry<string>();

registry.TryAddBuilder<string>("C", (_, b) => b.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder<string>("C", (b, _) => b.AddStrategy(new TestResilienceStrategy()));

registry.TryAdd("A", new TestResilienceStrategy<string>());
registry.TryAdd("B", new TestResilienceStrategy<string>());
Expand Down Expand Up @@ -107,7 +107,7 @@ public void Remove_Generic_Ok()
public void RemoveBuilder_Ok()
{
var registry = new ResilienceStrategyRegistry<string>();
registry.TryAddBuilder("A", (_, b) => b.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder("A", (b, _) => b.AddStrategy(new TestResilienceStrategy()));

registry.RemoveBuilder("A").Should().BeTrue();
registry.RemoveBuilder("A").Should().BeFalse();
Expand All @@ -119,7 +119,7 @@ public void RemoveBuilder_Ok()
public void RemoveBuilder_Generic_Ok()
{
var registry = new ResilienceStrategyRegistry<string>();
registry.TryAddBuilder<string>("A", (_, b) => b.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder<string>("A", (b, _) => b.AddStrategy(new TestResilienceStrategy()));

registry.RemoveBuilder<string>("A").Should().BeTrue();
registry.RemoveBuilder<string>("A").Should().BeFalse();
Expand All @@ -133,7 +133,7 @@ public void GetStrategy_BuilderMultiInstance_EnsureMultipleInstances()
var builderName = "A";
var registry = CreateRegistry();
var strategies = new HashSet<ResilienceStrategy>();
registry.TryAddBuilder(StrategyId.Create(builderName), (_, builder) => builder.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder(StrategyId.Create(builderName), (builder, _) => builder.AddStrategy(new TestResilienceStrategy()));

for (int i = 0; i < 100; i++)
{
Expand All @@ -154,7 +154,7 @@ public void GetStrategy_GenericBuilderMultiInstance_EnsureMultipleInstances()
var builderName = "A";
var registry = CreateRegistry();
var strategies = new HashSet<ResilienceStrategy<string>>();
registry.TryAddBuilder<string>(StrategyId.Create(builderName), (_, builder) => builder.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder<string>(StrategyId.Create(builderName), (builder, _) => builder.AddStrategy(new TestResilienceStrategy()));

for (int i = 0; i < 100; i++)
{
Expand All @@ -176,10 +176,10 @@ public void AddBuilder_GetStrategy_EnsureCalled()
_callback = _ => activatorCalls++;
var registry = CreateRegistry();
var called = 0;
registry.TryAddBuilder(StrategyId.Create("A"), (key, builder) =>
registry.TryAddBuilder(StrategyId.Create("A"), (builder, context) =>
{
builder.AddStrategy(new TestResilienceStrategy());
builder.Properties.Set(StrategyId.ResilienceKey, key);
builder.Properties.Set(StrategyId.ResilienceKey, context.StrategyKey);
called++;
});

Expand All @@ -205,10 +205,10 @@ public void AddBuilder_GenericGetStrategy_EnsureCalled()
_callback = _ => activatorCalls++;
var registry = CreateRegistry();
var called = 0;
registry.TryAddBuilder<string>(StrategyId.Create("A"), (key, builder) =>
registry.TryAddBuilder<string>(StrategyId.Create("A"), (builder, context) =>
{
builder.AddStrategy(new TestResilienceStrategy());
builder.Properties.Set(StrategyId.ResilienceKey, key);
builder.Properties.Set(StrategyId.ResilienceKey, context.StrategyKey);
called++;
});

Expand All @@ -235,8 +235,10 @@ public void AddBuilder_EnsureStrategyKey()

var called = false;
var registry = CreateRegistry();
registry.TryAddBuilder(StrategyId.Create("A"), (_, builder) =>
registry.TryAddBuilder(StrategyId.Create("A"), (builder, context) =>
{
context.BuilderName.Should().Be("A");
context.StrategyKeyString.Should().Be("Instance1");
builder.AddStrategy(new TestResilienceStrategy());
builder.BuilderName.Should().Be("A");
builder.Properties.TryGetValue(TelemetryUtil.StrategyKey, out var val).Should().BeTrue();
Expand All @@ -252,8 +254,8 @@ public void AddBuilder_EnsureStrategyKey()
public void AddBuilder_MultipleGeneric_EnsureDistinctInstances()
{
var registry = CreateRegistry();
registry.TryAddBuilder<string>(StrategyId.Create("A"), (_, builder) => builder.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder<int>(StrategyId.Create("A"), (_, builder) => builder.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder<string>(StrategyId.Create("A"), (builder, _) => builder.AddStrategy(new TestResilienceStrategy()));
registry.TryAddBuilder<int>(StrategyId.Create("A"), (builder, _) => builder.AddStrategy(new TestResilienceStrategy()));

registry.Get<string>(StrategyId.Create("A", "Instance1")).Should().BeSameAs(registry.Get<string>(StrategyId.Create("A", "Instance1")));
registry.Get<int>(StrategyId.Create("A", "Instance1")).Should().BeSameAs(registry.Get<int>(StrategyId.Create("A", "Instance1")));
Expand All @@ -267,7 +269,7 @@ public void AddBuilder_Generic_EnsureStrategyKey()

var called = false;
var registry = CreateRegistry();
registry.TryAddBuilder<string>(StrategyId.Create("A"), (_, builder) =>
registry.TryAddBuilder<string>(StrategyId.Create("A"), (builder, _) =>
{
builder.AddStrategy(new TestResilienceStrategy());
builder.BuilderName.Should().Be("A");
Expand Down
37 changes: 37 additions & 0 deletions src/Polly.Core/Registry/ConfigureBuilderContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Polly.Registry;

/// <summary>
/// The context used by <see cref="ResilienceStrategyRegistry{TKey}"/>.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
public class ConfigureBuilderContext<TKey>
where TKey : notnull
{
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBuilderContext{TKey}"/> class.
/// </summary>
/// <param name="strategyKey">The strategy key.</param>
/// <param name="builderName">The builder name.</param>
/// <param name="strategyKeyString">The strategy key as string.</param>
public ConfigureBuilderContext(TKey strategyKey, string builderName, string strategyKeyString)
{
StrategyKey = strategyKey;
BuilderName = builderName;
StrategyKeyString = strategyKeyString;
}

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

/// <summary>
/// Gets the builder name for the builder being used to create the strategy.
/// </summary>
public string BuilderName { get; }

/// <summary>
/// Gets the string representation of strategy key for the strategy being created.
/// </summary>
public string StrategyKeyString { get; }
}
14 changes: 5 additions & 9 deletions src/Polly.Core/Registry/ResilienceStrategyRegistry.TResult.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Polly.Telemetry;

namespace Polly.Registry;

Expand All @@ -9,7 +8,7 @@ public sealed partial class ResilienceStrategyRegistry<TKey> : ResilienceStrateg
private sealed class GenericRegistry<TResult>
{
private readonly Func<ResilienceStrategyBuilder<TResult>> _activator;
private readonly ConcurrentDictionary<TKey, Action<TKey, ResilienceStrategyBuilder<TResult>>> _builders;
private readonly ConcurrentDictionary<TKey, Action<ResilienceStrategyBuilder<TResult>, ConfigureBuilderContext<TKey>>> _builders;
private readonly ConcurrentDictionary<TKey, ResilienceStrategy<TResult>> _strategies;

private readonly Func<TKey, string> _strategyKeyFormatter;
Expand All @@ -23,7 +22,7 @@ public GenericRegistry(
Func<TKey, string> builderNameFormatter)
{
_activator = activator;
_builders = new ConcurrentDictionary<TKey, Action<TKey, ResilienceStrategyBuilder<TResult>>>(builderComparer);
_builders = new ConcurrentDictionary<TKey, Action<ResilienceStrategyBuilder<TResult>, ConfigureBuilderContext<TKey>>>(builderComparer);
_strategies = new ConcurrentDictionary<TKey, ResilienceStrategy<TResult>>(strategyComparer);
_strategyKeyFormatter = strategyKeyFormatter;
_builderNameFormatter = builderNameFormatter;
Expand All @@ -44,11 +43,8 @@ public bool TryGet(TKey key, [NotNullWhen(true)] out ResilienceStrategy<TResult>
{
strategy = _strategies.GetOrAdd(key, key =>
{
var builder = _activator();
builder.BuilderName = _builderNameFormatter(key);
builder.Properties.Set(TelemetryUtil.StrategyKey, _strategyKeyFormatter(key));
configure(key, builder);
return builder.Build();
var context = new ConfigureBuilderContext<TKey>(key, _builderNameFormatter(key), _strategyKeyFormatter(key));
return _strategies.GetOrAdd(key, key => new ResilienceStrategy<TResult>(CreateStrategy(_activator, context, configure)));
});

return true;
Expand All @@ -58,7 +54,7 @@ public bool TryGet(TKey key, [NotNullWhen(true)] out ResilienceStrategy<TResult>
return false;
}

public bool TryAddBuilder(TKey key, Action<TKey, ResilienceStrategyBuilder<TResult>> configure) => _builders.TryAdd(key, configure);
public bool TryAddBuilder(TKey key, Action<ResilienceStrategyBuilder<TResult>, ConfigureBuilderContext<TKey>> configure) => _builders.TryAdd(key, configure);

public bool RemoveBuilder(TKey key) => _builders.TryRemove(key, out _);

Expand Down
33 changes: 20 additions & 13 deletions src/Polly.Core/Registry/ResilienceStrategyRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using Polly.Strategy;
using Polly.Telemetry;

namespace Polly.Registry;
Expand All @@ -20,7 +21,7 @@ public sealed partial class ResilienceStrategyRegistry<TKey> : ResilienceStrateg
where TKey : notnull
{
private readonly Func<ResilienceStrategyBuilder> _activator;
private readonly ConcurrentDictionary<TKey, Action<TKey, ResilienceStrategyBuilder>> _builders;
private readonly ConcurrentDictionary<TKey, Action<ResilienceStrategyBuilder, ConfigureBuilderContext<TKey>>> _builders;
private readonly ConcurrentDictionary<TKey, ResilienceStrategy> _strategies;
private readonly ConcurrentDictionary<Type, object> _genericRegistry = new();

Expand Down Expand Up @@ -50,7 +51,7 @@ public ResilienceStrategyRegistry(ResilienceStrategyRegistryOptions<TKey> option
ValidationHelper.ValidateObject(options, "The resilience strategy registry options are invalid.");

_activator = options.BuilderFactory;
_builders = new ConcurrentDictionary<TKey, Action<TKey, ResilienceStrategyBuilder>>(options.BuilderComparer);
_builders = new ConcurrentDictionary<TKey, Action<ResilienceStrategyBuilder, ConfigureBuilderContext<TKey>>>(options.BuilderComparer);
_strategies = new ConcurrentDictionary<TKey, ResilienceStrategy>(options.StrategyComparer);
_strategyKeyFormatter = options.StrategyKeyFormatter;
_builderNameFormatter = options.BuilderNameFormatter;
Expand Down Expand Up @@ -118,15 +119,8 @@ public override bool TryGet(TKey key, [NotNullWhen(true)] out ResilienceStrategy

if (_builders.TryGetValue(key, out var configure))
{
strategy = _strategies.GetOrAdd(key, key =>
{
var builder = _activator();
builder.BuilderName = _builderNameFormatter(key);
builder.Properties.Set(TelemetryUtil.StrategyKey, _strategyKeyFormatter(key));
configure(key, builder);
return builder.Build();
});

var context = new ConfigureBuilderContext<TKey>(key, _builderNameFormatter(key), _strategyKeyFormatter(key));
strategy = _strategies.GetOrAdd(key, key => CreateStrategy(_activator, context, configure));
return true;
}

Expand All @@ -144,7 +138,7 @@ public override bool TryGet(TKey key, [NotNullWhen(true)] out ResilienceStrategy
/// Use this method when you want to create the strategy on-demand when it's first accessed.
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="configure"/> is <see langword="null"/>.</exception>
public bool TryAddBuilder(TKey key, Action<TKey, ResilienceStrategyBuilder> configure)
public bool TryAddBuilder(TKey key, Action<ResilienceStrategyBuilder, ConfigureBuilderContext<TKey>> configure)
{
Guard.NotNull(configure);

Expand All @@ -162,7 +156,7 @@ public bool TryAddBuilder(TKey key, Action<TKey, ResilienceStrategyBuilder> conf
/// Use this method when you want to create the strategy on-demand when it's first accessed.
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="configure"/> is <see langword="null"/>.</exception>
public bool TryAddBuilder<TResult>(TKey key, Action<TKey, ResilienceStrategyBuilder<TResult>> configure)
public bool TryAddBuilder<TResult>(TKey key, Action<ResilienceStrategyBuilder<TResult>, ConfigureBuilderContext<TKey>> configure)
{
Guard.NotNull(configure);

Expand Down Expand Up @@ -201,6 +195,19 @@ public bool TryAddBuilder<TResult>(TKey key, Action<TKey, ResilienceStrategyBuil
/// </remarks>
public void Clear<TResult>() => GetGenericRegistry<TResult>().Clear();

private static ResilienceStrategy CreateStrategy<TBuilder>(
Func<TBuilder> activator,
ConfigureBuilderContext<TKey> context,
Action<TBuilder, ConfigureBuilderContext<TKey>> configure)
where TBuilder : ResilienceStrategyBuilderBase
{
var builder = activator();
builder.BuilderName = context.BuilderName;
builder.Properties.Set(TelemetryUtil.StrategyKey, context.StrategyKeyString);
configure(builder, context);
return builder.BuildStrategy();
}

private GenericRegistry<TResult> GetGenericRegistry<TResult>()
{
if (_genericRegistry.TryGetValue(typeof(TResult), out var genericRegistry))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void AddResilienceStrategy_EnsureContextFilled(bool generic)
{
_services.AddResilienceStrategy<string, string>(Key, (builder, context) =>
{
context.Key.Should().Be(Key);
context.StrategyKey.Should().Be(Key);
builder.Should().NotBeNull();
context.ServiceProvider.Should().NotBeNull();
builder.AddStrategy(new TestStrategy());
Expand All @@ -108,7 +108,7 @@ public void AddResilienceStrategy_EnsureContextFilled(bool generic)
{
_services.AddResilienceStrategy(Key, (builder, context) =>
{
context.Key.Should().Be(Key);
context.StrategyKey.Should().Be(Key);
builder.Should().NotBeNull();
context.ServiceProvider.Should().NotBeNull();
builder.AddStrategy(new TestStrategy());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
using Polly.Registry;

namespace Polly.Extensions.DependencyInjection;

/// <summary>
/// Represents the context for adding a resilience strategy with the specified key.
/// </summary>
/// <typeparam name="TKey">The type of the key used to identify the resilience strategy.</typeparam>
public sealed class AddResilienceStrategyContext<TKey>
public sealed class AddResilienceStrategyContext<TKey> : ConfigureBuilderContext<TKey>
where TKey : notnull
{
internal AddResilienceStrategyContext(TKey key, IServiceProvider serviceProvider)
{
Key = key;
ServiceProvider = serviceProvider;
}

/// <summary>
/// Gets the key used to identify the resilience strategy.
/// </summary>
public TKey Key { get; }
internal AddResilienceStrategyContext(ConfigureBuilderContext<TKey> context, IServiceProvider serviceProvider)
: base(context.StrategyKey, context.BuilderName, context.StrategyKeyString) => ServiceProvider = serviceProvider;

/// <summary>
/// Gets the <see cref="IServiceProvider"/> that provides access to the dependency injection container.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ public static IServiceCollection AddResilienceStrategy<TKey, TResult>(
{
// the last added builder with the same key wins, this allows overriding the builders
registry.RemoveBuilder<TResult>(key);
registry.TryAddBuilder<TResult>(key, (key, builder) =>
registry.TryAddBuilder<TResult>(key, (builder, context) =>
{
configure(builder, new AddResilienceStrategyContext<TKey>(key, serviceProvider));
configure(builder, new AddResilienceStrategyContext<TKey>(context, serviceProvider));
});
});
});
Expand Down Expand Up @@ -151,9 +151,9 @@ public static IServiceCollection AddResilienceStrategy<TKey>(
{
// the last added builder with the same key wins, this allows overriding the builders
registry.RemoveBuilder(key);
registry.TryAddBuilder(key, (key, builder) =>
registry.TryAddBuilder(key, (builder, context) =>
{
configure(builder, new AddResilienceStrategyContext<TKey>(key, serviceProvider));
configure(builder, new AddResilienceStrategyContext<TKey>(context, serviceProvider));
});
});
});
Expand Down