sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

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

View File

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