Skip to content

Commit f6e09cc

Browse files
authored
The deafult RateLimiterStrategyOptions instance is now valid (#1315)
1 parent f7399c3 commit f6e09cc

File tree

5 files changed

+62
-11
lines changed

5 files changed

+62
-11
lines changed

src/Polly.RateLimiting/RateLimiterConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ namespace Polly.RateLimiting;
22

33
internal static class RateLimiterConstants
44
{
5+
public const int DefaultPermitLimit = 1000;
6+
7+
public const int DefaultQueueLimit = 0;
8+
59
public const string StrategyType = "RateLimiter";
610

711
public const string OnRateLimiterRejectedEvent = "OnRateLimiterRejected";

src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public static class RateLimiterResilienceStrategyBuilderExtensions
2020
/// <returns>The builder instance with the concurrency limiter strategy added.</returns>
2121
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> is <see langword="null"/>.</exception>
2222
/// <exception cref="ValidationException">Thrown when the options constructed from the arguments are invalid.</exception>
23+
/// <exception cref="ArgumentException">Thrown when <paramref name="permitLimit"/> or <paramref name="queueLimit"/> is invalid.</exception>
2324
public static TBuilder AddConcurrencyLimiter<TBuilder>(
2425
this TBuilder builder,
2526
int permitLimit,
@@ -44,6 +45,7 @@ public static TBuilder AddConcurrencyLimiter<TBuilder>(
4445
/// <returns>The builder instance with the concurrency limiter strategy added.</returns>
4546
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
4647
/// <exception cref="ValidationException">Thrown when the options constructed from the arguments are invalid.</exception>
48+
/// <exception cref="ArgumentException">Thrown when <paramref name="options"/> are invalid.</exception>
4749
public static TBuilder AddConcurrencyLimiter<TBuilder>(
4850
this TBuilder builder,
4951
ConcurrencyLimiterOptions options)
@@ -54,7 +56,7 @@ public static TBuilder AddConcurrencyLimiter<TBuilder>(
5456

5557
return builder.AddRateLimiter(new RateLimiterStrategyOptions
5658
{
57-
RateLimiter = new ConcurrencyLimiter(options),
59+
DefaultRateLimiterOptions = options
5860
});
5961
}
6062

@@ -90,6 +92,7 @@ public static TBuilder AddRateLimiter<TBuilder>(
9092
/// <returns>The builder instance with the rate limiter strategy added.</returns>
9193
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
9294
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> are invalid.</exception>
95+
/// <exception cref="ArgumentException">Thrown when <see cref="RateLimiterStrategyOptions.DefaultRateLimiterOptions"/> for <paramref name="options"/> are invalid.</exception>
9396
public static TBuilder AddRateLimiter<TBuilder>(
9497
this TBuilder builder,
9598
RateLimiterStrategyOptions options)
@@ -98,7 +101,15 @@ public static TBuilder AddRateLimiter<TBuilder>(
98101
Guard.NotNull(builder);
99102
Guard.NotNull(options);
100103

101-
builder.AddStrategy(context => new RateLimiterResilienceStrategy(options.RateLimiter!, options.OnRejected, context.Telemetry), options);
104+
builder.AddStrategy(
105+
context =>
106+
{
107+
return new RateLimiterResilienceStrategy(
108+
options.RateLimiter ?? new ConcurrencyLimiter(options.DefaultRateLimiterOptions),
109+
options.OnRejected,
110+
context.Telemetry);
111+
},
112+
options);
102113
return builder;
103114
}
104115
}

src/Polly.RateLimiting/RateLimiterStrategyOptions.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ public class RateLimiterStrategyOptions : ResilienceStrategyOptions
1414
/// <remarks>Returns <c>RateLimiter</c> value.</remarks>
1515
public sealed override string StrategyType => RateLimiterConstants.StrategyType;
1616

17+
/// <summary>
18+
/// Gets or sets the default rate limiter options.
19+
/// </summary>
20+
/// <remarks>
21+
/// The options for the default limiter that will be used when <see cref="RateLimiter"/> is <see langword="null"/>.
22+
/// <para>
23+
/// <see cref="ConcurrencyLimiterOptions.PermitLimit"/> defaults to 1000.
24+
/// <see cref="ConcurrencyLimiterOptions.QueueLimit"/> defaults to 0.
25+
/// </para>
26+
/// </remarks>
27+
[Required]
28+
public ConcurrencyLimiterOptions DefaultRateLimiterOptions { get; set; } = new()
29+
{
30+
QueueLimit = RateLimiterConstants.DefaultQueueLimit,
31+
PermitLimit = RateLimiterConstants.DefaultPermitLimit,
32+
};
33+
1734
/// <summary>
1835
/// Gets or sets an event that is raised when the execution of user-provided callback is rejected by the rate limiter.
1936
/// </summary>
@@ -26,8 +43,8 @@ public class RateLimiterStrategyOptions : ResilienceStrategyOptions
2643
/// Gets or sets the rate limiter that the strategy uses.
2744
/// </summary>
2845
/// <remarks>
29-
/// This property is required and defaults to <see langword="null"/>.
46+
/// Defaults to <see langword="null"/>. If this property is <see langword="null"/>,
47+
/// then the strategy will use a <see cref="ConcurrencyLimiter"/> created using <see cref="DefaultRateLimiterOptions"/>.
3048
/// </remarks>
31-
[Required]
3249
public RateLimiter? RateLimiter { get; set; }
3350
}

test/Polly.RateLimiting.Tests/RateLimiterResilienceStrategyBuilderExtensionsTests.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ public class RateLimiterResilienceStrategyBuilderExtensionsTests
2626
},
2727
builder =>
2828
{
29-
builder.AddRateLimiter(Mock.Of<RateLimiter>());
30-
AssertRateLimiter(builder, hasEvents: false);
29+
var expected = Mock.Of<RateLimiter>();
30+
builder.AddRateLimiter(expected);
31+
AssertRateLimiter(builder, hasEvents: false, limiter => limiter.Should().Be(expected));
3132
}
3233
};
3334

@@ -42,6 +43,20 @@ public void AddRateLimiter_Extensions_Ok(Action<ResilienceStrategyBuilder<int>>
4243
builder.Build().Should().BeOfType<RateLimiterResilienceStrategy>();
4344
}
4445

46+
[Fact]
47+
public void AddConcurrencyLimiter_InvalidOptions_Throws()
48+
{
49+
Assert.Throws<ArgumentException>(() =>
50+
{
51+
return new ResilienceStrategyBuilder().AddConcurrencyLimiter(new ConcurrencyLimiterOptions
52+
{
53+
PermitLimit = -10,
54+
QueueLimit = -10
55+
})
56+
.Build();
57+
});
58+
}
59+
4560
[Fact]
4661
public void AddRateLimiter_AllExtensions_Ok()
4762
{
@@ -71,28 +86,28 @@ public void AddRateLimiter_Ok()
7186
[Fact]
7287
public void AddRateLimiter_InvalidOptions_Throws()
7388
{
74-
new ResilienceStrategyBuilder().Invoking(b => b.AddRateLimiter(new RateLimiterStrategyOptions()))
89+
new ResilienceStrategyBuilder().Invoking(b => b.AddRateLimiter(new RateLimiterStrategyOptions { DefaultRateLimiterOptions = null! }))
7590
.Should()
7691
.Throw<ValidationException>()
7792
.WithMessage("""
7893
The 'RateLimiterStrategyOptions' are invalid.
7994
8095
Validation Errors:
81-
The RateLimiter field is required.
96+
The DefaultRateLimiterOptions field is required.
8297
""");
8398
}
8499

85100
[Fact]
86101
public void AddGenericRateLimiter_InvalidOptions_Throws()
87102
{
88-
new ResilienceStrategyBuilder<int>().Invoking(b => b.AddRateLimiter(new RateLimiterStrategyOptions()))
103+
new ResilienceStrategyBuilder<int>().Invoking(b => b.AddRateLimiter(new RateLimiterStrategyOptions { DefaultRateLimiterOptions = null! }))
89104
.Should()
90105
.Throw<ValidationException>()
91106
.WithMessage("""
92107
The 'RateLimiterStrategyOptions' are invalid.
93108
94109
Validation Errors:
95-
The RateLimiter field is required.
110+
The DefaultRateLimiterOptions field is required.
96111
""");
97112
}
98113

@@ -109,7 +124,7 @@ public void AddRateLimiter_Options_Ok()
109124
strategy.Should().BeOfType<RateLimiterResilienceStrategy>();
110125
}
111126

112-
private static void AssertRateLimiter(ResilienceStrategyBuilder<int> builder, bool hasEvents)
127+
private static void AssertRateLimiter(ResilienceStrategyBuilder<int> builder, bool hasEvents, Action<RateLimiter>? assertLimiter = null)
113128
{
114129
var strategy = GetResilienceStrategy(builder.Build());
115130
strategy.Limiter.Should().NotBeNull();
@@ -125,6 +140,8 @@ private static void AssertRateLimiter(ResilienceStrategyBuilder<int> builder, bo
125140
{
126141
strategy.OnLeaseRejected.Should().BeNull();
127142
}
143+
144+
assertLimiter?.Invoke(strategy.Limiter);
128145
}
129146

130147
private static void AssertConcurrencyLimiter(ResilienceStrategyBuilder<int> builder, bool hasEvents)

test/Polly.RateLimiting.Tests/RateLimiterStrategyOptionsTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ public void Ctor_EnsureDefaults()
1010
options.StrategyType.Should().Be(RateLimiterConstants.StrategyType);
1111
options.RateLimiter.Should().BeNull();
1212
options.OnRejected.Should().BeNull();
13+
options.DefaultRateLimiterOptions.PermitLimit.Should().Be(1000);
14+
options.DefaultRateLimiterOptions.QueueLimit.Should().Be(0);
1315
}
1416
}

0 commit comments

Comments
 (0)