2020-12-22 12:15:26 +00:00
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
|
|
using Serilog;
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
namespace Myriad.Rest.Ratelimit;
|
|
|
|
|
|
|
|
public class BucketManager: IDisposable
|
2020-12-22 12:15:26 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
private static readonly TimeSpan StaleBucketTimeout = TimeSpan.FromMinutes(5);
|
|
|
|
private static readonly TimeSpan PruneWorkerInterval = TimeSpan.FromMinutes(1);
|
|
|
|
private readonly ConcurrentDictionary<(string key, ulong major), Bucket> _buckets = new();
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private readonly ConcurrentDictionary<string, string> _endpointKeyMap = new();
|
|
|
|
private readonly ConcurrentDictionary<string, int> _knownKeyLimits = new();
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private readonly ILogger _logger;
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private readonly Task _worker;
|
|
|
|
private readonly CancellationTokenSource _workerCts = new();
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public BucketManager(ILogger logger)
|
|
|
|
{
|
|
|
|
_logger = logger.ForContext<BucketManager>();
|
|
|
|
_worker = PruneWorker(_workerCts.Token);
|
|
|
|
}
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
_workerCts.Dispose();
|
|
|
|
_worker.Dispose();
|
|
|
|
}
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public Bucket? GetBucket(string endpoint, ulong major)
|
|
|
|
{
|
|
|
|
if (!_endpointKeyMap.TryGetValue(endpoint, out var key))
|
|
|
|
return null;
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (_buckets.TryGetValue((key, major), out var bucket))
|
|
|
|
return bucket;
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (!_knownKeyLimits.TryGetValue(key, out var knownLimit))
|
|
|
|
return null;
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
_logger.Debug("Creating new bucket {BucketKey}/{BucketMajor} with limit {KnownLimit}", key, major,
|
|
|
|
knownLimit);
|
|
|
|
return _buckets.GetOrAdd((key, major),
|
|
|
|
k => new Bucket(_logger, k.Item1, k.Item2, knownLimit));
|
|
|
|
}
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public void UpdateEndpointInfo(string endpoint, string key, int? limit)
|
|
|
|
{
|
|
|
|
_endpointKeyMap[endpoint] = key;
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (limit != null)
|
|
|
|
_knownKeyLimits[key] = limit.Value;
|
|
|
|
}
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private async Task PruneWorker(CancellationToken ct)
|
|
|
|
{
|
|
|
|
while (!ct.IsCancellationRequested)
|
2020-12-22 12:15:26 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
await Task.Delay(PruneWorkerInterval, ct);
|
|
|
|
PruneStaleBuckets(DateTimeOffset.UtcNow);
|
2020-12-22 12:15:26 +00:00
|
|
|
}
|
2021-11-27 02:10:56 +00:00
|
|
|
}
|
2020-12-22 12:15:26 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private void PruneStaleBuckets(DateTimeOffset now)
|
|
|
|
{
|
|
|
|
foreach (var (key, bucket) in _buckets)
|
2020-12-22 12:15:26 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
if (now - bucket.LastUsed <= StaleBucketTimeout)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
_logger.Debug("Pruning unused bucket {BucketKey}/{BucketMajor} (last used at {BucketLastUsed})",
|
|
|
|
bucket.Key, bucket.Major, bucket.LastUsed);
|
|
|
|
_buckets.TryRemove(key, out _);
|
2020-12-22 12:15:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|