-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow listening for options changes (#1279)
* Allow listening for options changes * fixes * PR comments * Fix delayed task not being cancelled. * kill mutants * fixes
- Loading branch information
Showing
14 changed files
with
267 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
src/Polly.Extensions.Tests/ReloadableResilienceStrategyTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
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; | ||
|
||
public class ReloadableResilienceStrategyTests | ||
{ | ||
private static readonly ResiliencePropertyKey<string> TagKey = new("tests.tag"); | ||
|
||
[InlineData(null)] | ||
[InlineData("custom-name")] | ||
[InlineData("")] | ||
[Theory] | ||
public void AddResilienceStrategy_EnsureReloadable(string? name) | ||
{ | ||
var reloadableConfig = new ReloadableConfiguration(); | ||
reloadableConfig.Reload(new() { { "tag", "initial-tag" } }); | ||
var builder = new ConfigurationBuilder().Add(reloadableConfig); | ||
|
||
var services = new ServiceCollection(); | ||
|
||
if (name == null) | ||
{ | ||
services.Configure<ReloadableStrategyOptions>(builder.Build()); | ||
} | ||
else | ||
{ | ||
services.Configure<ReloadableStrategyOptions>(name, builder.Build()); | ||
} | ||
|
||
services.AddResilienceStrategy("my-strategy", (builder, context) => | ||
{ | ||
var options = context.GetOptions<ReloadableStrategyOptions>(name); | ||
context.EnableReloads<ReloadableStrategyOptions>(name); | ||
|
||
builder.AddStrategy(_ => new ReloadableStrategy(options.Tag), new ReloadableStrategyOptions()); | ||
}); | ||
|
||
var serviceProvider = services.BuildServiceProvider(); | ||
var strategy = serviceProvider.GetRequiredService<ResilienceStrategyProvider<string>>().Get("my-strategy"); | ||
var context = ResilienceContext.Get(); | ||
var registry = serviceProvider.GetRequiredService<OptionsReloadHelperRegistry<string>>(); | ||
|
||
// initial | ||
strategy.Execute(_ => "dummy", context); | ||
context.Properties.GetValue(TagKey, string.Empty).Should().Be("initial-tag"); | ||
|
||
// reloads | ||
for (int i = 0; i < 10; i++) | ||
{ | ||
reloadableConfig.Reload(new() { { "tag", $"reload-{i}" } }); | ||
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 | ||
{ | ||
public ReloadableStrategy(string tag) => Tag = tag; | ||
|
||
public string Tag { get; } | ||
|
||
protected override ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, TState>( | ||
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback, | ||
ResilienceContext context, | ||
TState state) | ||
{ | ||
context.Properties.Set(TagKey, Tag); | ||
return callback(context, state); | ||
} | ||
} | ||
|
||
public class ReloadableStrategyOptions : ResilienceStrategyOptions | ||
{ | ||
public override string StrategyType => "Reloadable"; | ||
|
||
public string Tag { get; set; } = string.Empty; | ||
} | ||
|
||
private class ReloadableConfiguration : ConfigurationProvider, IConfigurationSource | ||
{ | ||
public IConfigurationProvider Build(IConfigurationBuilder builder) | ||
{ | ||
return this; | ||
} | ||
|
||
public void Reload(Dictionary<string, string?> data) | ||
{ | ||
Data = new Dictionary<string, string?>(data, StringComparer.OrdinalIgnoreCase); | ||
OnReload(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
using System.Threading.Tasks; | ||
using Polly.Extensions.Telemetry; | ||
|
||
namespace Polly.Extensions.Tests.Telemetry; | ||
|
1 change: 0 additions & 1 deletion
1
src/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
using System.Diagnostics.Metrics; | ||
using Microsoft.Extensions.Logging; | ||
using Polly.Extensions.Telemetry; | ||
|
||
|
2 changes: 0 additions & 2 deletions
2
src/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/Polly.Extensions.Tests/Utils/OptionsReloadHelperTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using Microsoft.Extensions.Options; | ||
using Moq; | ||
using Polly.Extensions.Utils; | ||
|
||
namespace Polly.Extensions.Tests.Utils; | ||
|
||
public class OptionsReloadHelperTests | ||
{ | ||
[Fact] | ||
public void Ctor_NamedOptions() | ||
{ | ||
var monitor = new Mock<IOptionsMonitor<string>>(); | ||
|
||
monitor | ||
.Setup(m => m.OnChange(It.IsAny<Action<string, string?>>())) | ||
.Returns(() => Mock.Of<IDisposable>()); | ||
|
||
using var helper = new OptionsReloadHelper<string>(monitor.Object, "name"); | ||
|
||
monitor.VerifyAll(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
using System; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Polly.Extensions.Telemetry; | ||
|
2 changes: 0 additions & 2 deletions
2
src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Polly.Extensions.Utils; | ||
|
||
internal sealed class OptionsReloadHelper<T> : IDisposable | ||
{ | ||
private readonly IDisposable? _listener; | ||
private CancellationTokenSource _cancellation = new(); | ||
|
||
public OptionsReloadHelper(IOptionsMonitor<T> monitor, string name) => _listener = monitor.OnChange((_, changedNamed) => | ||
{ | ||
if (name == changedNamed) | ||
{ | ||
HandleChange(); | ||
} | ||
}); | ||
|
||
public CancellationToken GetCancellationToken() => _cancellation.Token; | ||
|
||
public void Dispose() | ||
{ | ||
_cancellation.Dispose(); | ||
_listener?.Dispose(); | ||
} | ||
|
||
private void HandleChange() | ||
{ | ||
var oldCancellation = _cancellation; | ||
_cancellation = new CancellationTokenSource(); | ||
oldCancellation.Cancel(); | ||
oldCancellation.Dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Polly.Extensions.Utils; | ||
|
||
internal sealed class OptionsReloadHelperRegistry<TKey> : IDisposable | ||
{ | ||
private readonly IServiceProvider _serviceProvider; | ||
private readonly ConcurrentDictionary<(Type optionsType, TKey key, string? name), object> _helpers = new(); | ||
|
||
public OptionsReloadHelperRegistry(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; | ||
|
||
public int Count => _helpers.Count; | ||
|
||
public OptionsReloadHelper<TOptions> Get<TOptions>(TKey key, string? name) | ||
{ | ||
name ??= Options.DefaultName; | ||
|
||
return (OptionsReloadHelper<TOptions>)_helpers.GetOrAdd((typeof(TOptions), key, name), _ => | ||
{ | ||
return new OptionsReloadHelper<TOptions>(_serviceProvider.GetRequiredService<IOptionsMonitor<TOptions>>(), name); | ||
}); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
foreach (var helper in _helpers.Values.OfType<IDisposable>()) | ||
{ | ||
helper.Dispose(); | ||
} | ||
|
||
_helpers.Clear(); | ||
} | ||
} |