mirror of https://github.com/abpframework/abp.git
7 changed files with 238 additions and 12 deletions
@ -0,0 +1,102 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.ExceptionHandling; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.OperationRateLimiting; |
|||
|
|||
/// <summary>
|
|||
/// A mock store that simulates a multi-rule Phase 2 race condition:
|
|||
/// - GetAsync always reports quota available (Phase 1 passes for all rules).
|
|||
/// - IncrementAsync succeeds for the first call, fails on the second call
|
|||
/// (simulating a concurrent race on Rule2), and tracks total increment calls
|
|||
/// so tests can verify that Rule3 was never incremented (early break).
|
|||
/// </summary>
|
|||
internal class MultiRuleRaceConditionSimulatorStore : IOperationRateLimitingStore |
|||
{ |
|||
private int _incrementCallCount; |
|||
|
|||
/// <summary>
|
|||
/// Total number of IncrementAsync calls made.
|
|||
/// </summary>
|
|||
public int IncrementCallCount => _incrementCallCount; |
|||
|
|||
public Task<OperationRateLimitingStoreResult> GetAsync(string key, TimeSpan duration, int maxCount) |
|||
{ |
|||
return Task.FromResult(new OperationRateLimitingStoreResult |
|||
{ |
|||
IsAllowed = true, |
|||
CurrentCount = 0, |
|||
MaxCount = maxCount |
|||
}); |
|||
} |
|||
|
|||
public Task<OperationRateLimitingStoreResult> IncrementAsync(string key, TimeSpan duration, int maxCount) |
|||
{ |
|||
var callIndex = Interlocked.Increment(ref _incrementCallCount); |
|||
|
|||
if (callIndex == 2) |
|||
{ |
|||
// Second rule: simulate concurrent race - another request consumed the last slot.
|
|||
return Task.FromResult(new OperationRateLimitingStoreResult |
|||
{ |
|||
IsAllowed = false, |
|||
CurrentCount = maxCount, |
|||
MaxCount = maxCount, |
|||
RetryAfter = duration |
|||
}); |
|||
} |
|||
|
|||
// First rule (and any others if early break fails): succeed.
|
|||
return Task.FromResult(new OperationRateLimitingStoreResult |
|||
{ |
|||
IsAllowed = true, |
|||
CurrentCount = 1, |
|||
MaxCount = maxCount |
|||
}); |
|||
} |
|||
|
|||
public Task ResetAsync(string key) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
[DependsOn( |
|||
typeof(AbpOperationRateLimitingModule), |
|||
typeof(AbpExceptionHandlingModule), |
|||
typeof(AbpTestBaseModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class AbpOperationRateLimitingPhase2EarlyBreakTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Replace( |
|||
ServiceDescriptor.Singleton<IOperationRateLimitingStore, MultiRuleRaceConditionSimulatorStore>()); |
|||
|
|||
Configure<AbpOperationRateLimitingOptions>(options => |
|||
{ |
|||
// 3-rule composite policy: all PartitionByParameter with different durations
|
|||
// so they generate unique cache keys and don't trigger duplicate rule validation.
|
|||
options.AddPolicy("TestMultiRuleRacePolicy", policy => |
|||
{ |
|||
policy.AddRule(rule => rule |
|||
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 5) |
|||
.PartitionByParameter()); |
|||
|
|||
policy.AddRule(rule => rule |
|||
.WithFixedWindow(TimeSpan.FromHours(2), maxCount: 5) |
|||
.PartitionByParameter()); |
|||
|
|||
policy.AddRule(rule => rule |
|||
.WithFixedWindow(TimeSpan.FromHours(3), maxCount: 5) |
|||
.PartitionByParameter()); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue