finish off sprint advisories and sprints
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace StellaOps.Router.Gateway.RateLimit;
|
||||
|
||||
@@ -51,7 +50,7 @@ public sealed class InstanceRateLimiter : IDisposable
|
||||
return RateLimitDecision.Allow(RateLimitScope.Instance, 0, 0, 0, key);
|
||||
}
|
||||
|
||||
var perMicroserviceCounters = _counters.GetOrAdd(key, _ => new MicroserviceCounters());
|
||||
var perMicroserviceCounters = _counters.GetOrAdd(key, _ => new MicroserviceCounters(_timeProvider));
|
||||
|
||||
RuleOutcome? mostRestrictiveViolation = null;
|
||||
RuleOutcome? closestToLimitAllowed = null;
|
||||
@@ -192,21 +191,25 @@ internal sealed class SlidingWindowCounter
|
||||
private readonly int _windowSeconds;
|
||||
private readonly int _bucketCount;
|
||||
private readonly long[] _buckets;
|
||||
private readonly long _bucketDurationStopwatchTicks;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly long _bucketDurationTicks;
|
||||
private long _lastBucketNumber;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public SlidingWindowCounter(int windowSeconds, int bucketCount = 10)
|
||||
public SlidingWindowCounter(int windowSeconds, TimeProvider? timeProvider = null, int bucketCount = 10)
|
||||
{
|
||||
_windowSeconds = Math.Max(1, windowSeconds);
|
||||
_bucketCount = Math.Max(1, bucketCount);
|
||||
_buckets = new long[_bucketCount];
|
||||
_bucketDurationStopwatchTicks = Math.Max(
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_bucketDurationTicks = Math.Max(
|
||||
1,
|
||||
(long)Math.Ceiling(Stopwatch.Frequency * ((double)_windowSeconds / _bucketCount)));
|
||||
_lastBucketNumber = Stopwatch.GetTimestamp() / _bucketDurationStopwatchTicks;
|
||||
(long)Math.Ceiling(TimeSpan.TicksPerSecond * ((double)_windowSeconds / _bucketCount)));
|
||||
_lastBucketNumber = GetCurrentTicks() / _bucketDurationTicks;
|
||||
}
|
||||
|
||||
private long GetCurrentTicks() => _timeProvider.GetUtcNow().Ticks;
|
||||
|
||||
/// <summary>
|
||||
/// Try to increment the counter. Returns (allowed, currentCount).
|
||||
/// </summary>
|
||||
@@ -251,11 +254,11 @@ internal sealed class SlidingWindowCounter
|
||||
if (total <= limit)
|
||||
return 0;
|
||||
|
||||
var now = Stopwatch.GetTimestamp();
|
||||
var currentBucketNumber = now / _bucketDurationStopwatchTicks;
|
||||
var now = GetCurrentTicks();
|
||||
var currentBucketNumber = now / _bucketDurationTicks;
|
||||
var currentBucketIndex = (int)(currentBucketNumber % _bucketCount);
|
||||
var currentBucketStart = currentBucketNumber * _bucketDurationStopwatchTicks;
|
||||
var ticksUntilNextBoundary = _bucketDurationStopwatchTicks - (now - currentBucketStart);
|
||||
var currentBucketStart = currentBucketNumber * _bucketDurationTicks;
|
||||
var ticksUntilNextBoundary = _bucketDurationTicks - (now - currentBucketStart);
|
||||
|
||||
var remaining = total;
|
||||
for (var i = 1; i <= _bucketCount; i++)
|
||||
@@ -265,8 +268,8 @@ internal sealed class SlidingWindowCounter
|
||||
|
||||
if (remaining <= limit)
|
||||
{
|
||||
var ticksUntilWithinLimit = ticksUntilNextBoundary + (i - 1) * _bucketDurationStopwatchTicks;
|
||||
var secondsUntilWithinLimit = (int)Math.Ceiling(ticksUntilWithinLimit / (double)Stopwatch.Frequency);
|
||||
var ticksUntilWithinLimit = ticksUntilNextBoundary + (i - 1) * _bucketDurationTicks;
|
||||
var secondsUntilWithinLimit = (int)Math.Ceiling(ticksUntilWithinLimit / (double)TimeSpan.TicksPerSecond);
|
||||
return Math.Max(1, secondsUntilWithinLimit);
|
||||
}
|
||||
}
|
||||
@@ -289,8 +292,8 @@ internal sealed class SlidingWindowCounter
|
||||
|
||||
private void RotateBuckets()
|
||||
{
|
||||
var now = Stopwatch.GetTimestamp();
|
||||
var currentBucketNumber = now / _bucketDurationStopwatchTicks;
|
||||
var now = GetCurrentTicks();
|
||||
var currentBucketNumber = now / _bucketDurationTicks;
|
||||
var bucketsToRotate = currentBucketNumber - _lastBucketNumber;
|
||||
|
||||
if (bucketsToRotate <= 0) return;
|
||||
@@ -308,7 +311,7 @@ internal sealed class SlidingWindowCounter
|
||||
|
||||
private int GetCurrentBucketIndex()
|
||||
{
|
||||
var now = Stopwatch.GetTimestamp();
|
||||
return (int)((now / _bucketDurationStopwatchTicks) % _bucketCount);
|
||||
var now = GetCurrentTicks();
|
||||
return (int)((now / _bucketDurationTicks) % _bucketCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class InstanceRateLimiterTests : IDisposable
|
||||
decision.RetryAfterSeconds.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact(Skip = "SlidingWindowCounter uses Stopwatch.GetTimestamp() internally which doesn't respect FakeTimeProvider. Requires refactoring SlidingWindowCounter to use TimeProvider.")]
|
||||
[Fact]
|
||||
public void TryAcquire_AfterWindowExpires_AllowsAgain()
|
||||
{
|
||||
// Arrange - exhaust the per-second limit
|
||||
|
||||
Reference in New Issue
Block a user