finish off sprint advisories and sprints

This commit is contained in:
master
2026-01-24 00:12:43 +02:00
parent 726d70dc7f
commit c70e83719e
266 changed files with 46699 additions and 1328 deletions

View File

@@ -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);
}
}

View File

@@ -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