init
This commit is contained in:
78
Jellyfin.Plugin.RedditComments/Services/RateLimiter.cs
Normal file
78
Jellyfin.Plugin.RedditComments/Services/RateLimiter.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jellyfin.Plugin.RedditComments.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Sliding-window rate limiter. Guarantees no more than N requests per rolling 60 seconds,
|
||||
/// no matter how many callers are waiting.
|
||||
/// </summary>
|
||||
public class RateLimiter
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly Queue<DateTime> _requestTimestamps = new Queue<DateTime>();
|
||||
private readonly int? _maxPerMinuteOverride;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RateLimiter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="maxPerMinuteOverride">Optional fixed limit (used by tests); otherwise the plugin configuration is used.</param>
|
||||
public RateLimiter(int? maxPerMinuteOverride = null)
|
||||
{
|
||||
_maxPerMinuteOverride = maxPerMinuteOverride;
|
||||
}
|
||||
|
||||
private int MaxPerMinute
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_maxPerMinuteOverride.HasValue)
|
||||
{
|
||||
return _maxPerMinuteOverride.Value;
|
||||
}
|
||||
|
||||
return ReadConfiguredLimit();
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReadConfiguredLimit()
|
||||
{
|
||||
var configured = Plugin.Instance?.Configuration.MaxRequestsPerMinute ?? 60;
|
||||
// Reddit's own hard limit for OAuth clients is 100 req/min; never exceed it.
|
||||
return Math.Clamp(configured, 1, 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until a request slot is available, then reserves it.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that completes when the caller may perform one request.</returns>
|
||||
public async Task WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
TimeSpan delay;
|
||||
lock (_lock)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
while (_requestTimestamps.Count > 0 && now - _requestTimestamps.Peek() >= TimeSpan.FromMinutes(1))
|
||||
{
|
||||
_requestTimestamps.Dequeue();
|
||||
}
|
||||
|
||||
if (_requestTimestamps.Count < MaxPerMinute)
|
||||
{
|
||||
_requestTimestamps.Enqueue(now);
|
||||
return;
|
||||
}
|
||||
|
||||
// Window is full: wait until the oldest request falls out of the window.
|
||||
delay = TimeSpan.FromMinutes(1) - (now - _requestTimestamps.Peek()) + TimeSpan.FromMilliseconds(100);
|
||||
}
|
||||
|
||||
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user