rdt-client/server/RdtClient.Service/Helpers/RateLimitCoordinator.cs
2026-03-14 13:41:30 -06:00

94 lines
2.3 KiB
C#

using System.Collections.Concurrent;
namespace RdtClient.Service.Helpers;
public class RateLimitCoordinator : IRateLimitCoordinator
{
private readonly ConcurrentDictionary<String, DateTimeOffset> _cooldowns = new();
public TimeSpan GetRemainingCooldown(String key)
{
if (_cooldowns.TryGetValue(key, out var nextAllowedAt))
{
var remaining = nextAllowedAt - DateTimeOffset.UtcNow;
if (remaining > TimeSpan.Zero)
{
return remaining;
}
_cooldowns.TryRemove(key, out _);
}
return TimeSpan.Zero;
}
public TimeSpan GetMaxRemainingCooldown()
{
var now = DateTimeOffset.UtcNow;
var max = TimeSpan.Zero;
foreach (var (key, nextAllowedAt) in _cooldowns)
{
var remaining = nextAllowedAt - now;
if (remaining > TimeSpan.Zero)
{
if (remaining > max)
{
max = remaining;
}
}
else
{
_cooldowns.TryRemove(key, out _);
}
}
return max;
}
public DateTimeOffset? GetMaxNextAllowedAt()
{
var now = DateTimeOffset.UtcNow;
DateTimeOffset? max = null;
foreach (var (key, nextAllowedAt) in _cooldowns)
{
if (nextAllowedAt > now)
{
if (max == null || nextAllowedAt > max)
{
max = nextAllowedAt;
}
}
else
{
_cooldowns.TryRemove(key, out _);
}
}
return max;
}
public void UpdateCooldown(String key, TimeSpan retryAfter)
{
var nextAllowedAt = DateTimeOffset.UtcNow.Add(retryAfter);
_cooldowns.AddOrUpdate(key, nextAllowedAt, (_, existing) => nextAllowedAt > existing ? nextAllowedAt : existing);
}
public DateTimeOffset? GetNextAllowedAt(String key)
{
if (_cooldowns.TryGetValue(key, out var nextAllowedAt))
{
if (nextAllowedAt > DateTimeOffset.UtcNow)
{
return nextAllowedAt;
}
_cooldowns.TryRemove(key, out _);
}
return null;
}
}