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;
///
/// 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).
///
internal class MultiRuleRaceConditionSimulatorStore : IOperationRateLimitingStore
{
private int _incrementCallCount;
///
/// Total number of IncrementAsync calls made.
///
public int IncrementCallCount => _incrementCallCount;
public Task GetAsync(string key, TimeSpan duration, int maxCount)
{
return Task.FromResult(new OperationRateLimitingStoreResult
{
IsAllowed = true,
CurrentCount = 0,
MaxCount = maxCount
});
}
public Task 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());
Configure(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());
});
});
}
}