sprints and audit work
This commit is contained in:
@@ -7,6 +7,8 @@ namespace StellaOps.Orchestrator.Core.RateLimiting;
|
||||
public sealed class BackpressureHandler
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly Func<double> _jitterSource;
|
||||
private int _consecutiveFailures;
|
||||
private DateTimeOffset? _backoffUntil;
|
||||
private DateTimeOffset _lastFailureAt;
|
||||
@@ -41,7 +43,7 @@ public sealed class BackpressureHandler
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _backoffUntil.HasValue && DateTimeOffset.UtcNow < _backoffUntil.Value;
|
||||
return _backoffUntil.HasValue && _timeProvider.GetUtcNow() < _backoffUntil.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +74,7 @@ public sealed class BackpressureHandler
|
||||
if (!_backoffUntil.HasValue)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
var remaining = _backoffUntil.Value - DateTimeOffset.UtcNow;
|
||||
var remaining = _backoffUntil.Value - _timeProvider.GetUtcNow();
|
||||
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
@@ -85,16 +87,22 @@ public sealed class BackpressureHandler
|
||||
/// <param name="maxDelay">Maximum delay cap.</param>
|
||||
/// <param name="failureThreshold">Failures before entering backoff.</param>
|
||||
/// <param name="jitterFactor">Random jitter factor (0.0 to 1.0).</param>
|
||||
/// <param name="timeProvider">Time provider for testability.</param>
|
||||
/// <param name="jitterSource">Jitter source for testability (returns 0.0-1.0).</param>
|
||||
public BackpressureHandler(
|
||||
TimeSpan? baseDelay = null,
|
||||
TimeSpan? maxDelay = null,
|
||||
int failureThreshold = 1,
|
||||
double jitterFactor = 0.2)
|
||||
double jitterFactor = 0.2,
|
||||
TimeProvider? timeProvider = null,
|
||||
Func<double>? jitterSource = null)
|
||||
{
|
||||
BaseDelay = baseDelay ?? TimeSpan.FromSeconds(1);
|
||||
MaxDelay = maxDelay ?? TimeSpan.FromMinutes(5);
|
||||
FailureThreshold = failureThreshold > 0 ? failureThreshold : 1;
|
||||
JitterFactor = Math.Clamp(jitterFactor, 0.0, 1.0);
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_jitterSource = jitterSource ?? Random.Shared.NextDouble;
|
||||
|
||||
if (BaseDelay <= TimeSpan.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(baseDelay), "Base delay must be positive.");
|
||||
@@ -107,11 +115,11 @@ public sealed class BackpressureHandler
|
||||
/// </summary>
|
||||
/// <param name="statusCode">HTTP status code from upstream.</param>
|
||||
/// <param name="retryAfter">Optional Retry-After header value.</param>
|
||||
/// <param name="now">Current time.</param>
|
||||
/// <param name="now">Current time (uses injected TimeProvider if not specified).</param>
|
||||
/// <returns>Backoff result with recommended delay.</returns>
|
||||
public BackpressureResult RecordFailure(int statusCode, TimeSpan? retryAfter = null, DateTimeOffset? now = null)
|
||||
{
|
||||
var timestamp = now ?? DateTimeOffset.UtcNow;
|
||||
var timestamp = now ?? _timeProvider.GetUtcNow();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -162,11 +170,11 @@ public sealed class BackpressureHandler
|
||||
/// <summary>
|
||||
/// Checks if a request should be allowed based on backoff state.
|
||||
/// </summary>
|
||||
/// <param name="now">Current time.</param>
|
||||
/// <param name="now">Current time (uses injected TimeProvider if not specified).</param>
|
||||
/// <returns>True if request should proceed, false if in backoff.</returns>
|
||||
public bool ShouldAllow(DateTimeOffset? now = null)
|
||||
{
|
||||
var timestamp = now ?? DateTimeOffset.UtcNow;
|
||||
var timestamp = now ?? _timeProvider.GetUtcNow();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -199,11 +207,11 @@ public sealed class BackpressureHandler
|
||||
/// <summary>
|
||||
/// Gets a snapshot of the current backpressure state.
|
||||
/// </summary>
|
||||
/// <param name="now">Current time.</param>
|
||||
/// <param name="now">Current time (uses injected TimeProvider if not specified).</param>
|
||||
/// <returns>Snapshot of backpressure state.</returns>
|
||||
public BackpressureSnapshot GetSnapshot(DateTimeOffset? now = null)
|
||||
{
|
||||
var timestamp = now ?? DateTimeOffset.UtcNow;
|
||||
var timestamp = now ?? _timeProvider.GetUtcNow();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -226,10 +234,10 @@ public sealed class BackpressureHandler
|
||||
var exponent = Math.Min(failures - 1, 10); // Cap exponent to prevent overflow
|
||||
var delayMs = BaseDelay.TotalMilliseconds * Math.Pow(2, exponent);
|
||||
|
||||
// Add jitter
|
||||
// Add jitter using injectable source for testability
|
||||
if (JitterFactor > 0)
|
||||
{
|
||||
var jitter = delayMs * JitterFactor * Random.Shared.NextDouble();
|
||||
var jitter = delayMs * JitterFactor * _jitterSource();
|
||||
delayMs += jitter;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,9 @@ public sealed record RetryPolicy(
|
||||
/// Calculates backoff duration in seconds for a given attempt.
|
||||
/// </summary>
|
||||
/// <param name="attempt">Attempt number (1-based).</param>
|
||||
/// <param name="jitterSource">Optional jitter source for testability (returns 0.0-1.0).</param>
|
||||
/// <returns>Backoff duration in seconds.</returns>
|
||||
public double CalculateBackoffSeconds(int attempt)
|
||||
public double CalculateBackoffSeconds(int attempt, Func<double>? jitterSource = null)
|
||||
{
|
||||
if (attempt < 1)
|
||||
{
|
||||
@@ -101,8 +102,9 @@ public sealed record RetryPolicy(
|
||||
// Cap at maximum
|
||||
var cappedBackoff = Math.Min(exponentialBackoff, MaxBackoffSeconds);
|
||||
|
||||
// Add jitter to prevent thundering herd
|
||||
var jitter = cappedBackoff * JitterFactor * (Random.Shared.NextDouble() * 2 - 1);
|
||||
// Add jitter to prevent thundering herd (use injectable source for testability)
|
||||
var randomValue = (jitterSource ?? Random.Shared.NextDouble)();
|
||||
var jitter = cappedBackoff * JitterFactor * (randomValue * 2 - 1);
|
||||
var finalBackoff = Math.Max(0, cappedBackoff + jitter);
|
||||
|
||||
return finalBackoff;
|
||||
|
||||
Reference in New Issue
Block a user