Skip to content

Commit

Permalink
ResilienceStrategyRegistry now uses context to configure the builder
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed May 31, 2023
1 parent 2b7fbf2 commit 95c903c
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 53 deletions.
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

0 comments on commit 95c903c

Please sign in to comment.