Skip to content

Commit

Permalink
fix: non-blocking rate-limit for anidb commands
Browse files Browse the repository at this point in the history
Switch from using a monitor and thread sleep (both sync) while rate limiting to using a semaphore slim and thread delay (both async) to not block the thread while we wait for the delay to elapse.
  • Loading branch information
revam committed Oct 30, 2024
1 parent 2e08d8c commit f06a89b
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 14 deletions.
18 changes: 8 additions & 10 deletions Shoko.Server/Providers/AniDB/AniDBRateLimiter.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace Shoko.Server.Providers.AniDB;

public abstract class AniDBRateLimiter
{
private readonly ILogger _logger;
private readonly object _lock = new();
private readonly SemaphoreSlim _lock = new(1, 1);
private readonly Stopwatch _requestWatch = new();
private readonly Stopwatch _activeTimeWatch = new();

Expand Down Expand Up @@ -42,14 +43,11 @@ private void ResetRate()
_logger.LogTrace("Rate is reset. Active time was {Time} ms", elapsedTime);
}

public T EnsureRate<T>(Func<T> action)
public async Task<T> EnsureRateAsync<T>(Func<Task<T>> action)
{
await _lock.WaitAsync();
try
{
var entered = false;
Monitor.Enter(_lock, ref entered);
if (!entered) throw new SynchronizationLockException();

var delay = _requestWatch.ElapsedMilliseconds;
if (delay > ResetPeriod) ResetRate();
var currentDelay = _activeTimeWatch.ElapsedMilliseconds > ShortPeriod ? LongDelay : ShortDelay;
Expand All @@ -58,22 +56,22 @@ public T EnsureRate<T>(Func<T> action)
{
_logger.LogTrace("Time since last request is {Delay} ms, not throttling", delay);
_logger.LogTrace("Sending AniDB command");
return action();
return await action();
}

// add 50ms for good measure
var waitTime = currentDelay - (int)delay + 50;

_logger.LogTrace("Time since last request is {Delay} ms, throttling for {Time}", delay, waitTime);
Thread.Sleep(waitTime);
await Task.Delay(waitTime);

_logger.LogTrace("Sending AniDB command");
return action();
return await action();
}
finally
{
_requestWatch.Restart();
Monitor.Exit(_lock);
_lock.Release();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ public async Task<HttpResponse<string>> GetHttpDirectly(string url)
{
throw new AniDBBannedException
{
BanType = UpdateType.HTTPBan, BanExpires = BanTime?.AddHours(BanTimerResetLength)
BanType = UpdateType.HTTPBan,
BanExpires = BanTime?.AddHours(BanTimerResetLength),
};
}

var response = await RateLimiter.EnsureRate(async () =>
var response = await RateLimiter.EnsureRateAsync(async () =>
{
using var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
Expand All @@ -63,7 +64,8 @@ public async Task<HttpResponse<string>> GetHttpDirectly(string url)
{
throw new AniDBBannedException
{
BanType = UpdateType.HTTPBan, BanExpires = BanTime?.AddHours(BanTimerResetLength)
BanType = UpdateType.HTTPBan,
BanExpires = BanTime?.AddHours(BanTimerResetLength),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ private async Task<string> SendInternal(string command, bool needsUnicode = true
Logger.LogWarning("AniDB request timed out. Checking Network and trying again");
await _connectivityService.CheckAvailability();
});
var result = await timeoutPolicy.ExecuteAndCaptureAsync(async () => await RateLimiter.EnsureRate(async () =>
var result = await timeoutPolicy.ExecuteAndCaptureAsync(async () => await RateLimiter.EnsureRateAsync(async () =>
{
if (_connectivityService.NetworkAvailability < NetworkAvailability.PartialInternet)
{
Expand Down

0 comments on commit f06a89b

Please sign in to comment.