Skip to content

Commit

Permalink
refactor: make AniDB rate limiting configurable
Browse files Browse the repository at this point in the history
Make the AniDB rate limiting configurable and tweak the defaults (again) to be less preservative for HTTP.

Also cached the values per rate limiter (UDP and HTTP) and added cache invalidation when the settings has been saved to re-apply the (potentially) updated settings onto the locally cached values.
  • Loading branch information
revam committed Nov 3, 2024
1 parent 5c36776 commit 9cc0c51
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 21 deletions.
107 changes: 102 additions & 5 deletions Shoko.Server/Providers/AniDB/AniDBRateLimiter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,134 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Shoko.Plugin.Abstractions;
using Shoko.Plugin.Abstractions.Events;
using Shoko.Server.Settings;

using ISettingsProvider = Shoko.Server.Settings.ISettingsProvider;

#nullable enable
namespace Shoko.Server.Providers.AniDB;

public abstract class AniDBRateLimiter
{
private readonly ILogger _logger;

private readonly SemaphoreSlim _lock = new(1, 1);

private readonly object _settingsLock = new();

private readonly Stopwatch _requestWatch = new();

private readonly Stopwatch _activeTimeWatch = new();

private readonly ISettingsProvider _settingsProvider;

private readonly IShokoEventHandler _eventHandler;

private readonly Func<IServerSettings, AnidbRateLimitSettings> _settingsSelector;

private int? _shortDelay = null;

// From AniDB's wiki about UDP rate limiting:
// Short Term:
// A Client MUST NOT send more than 0.5 packets per second(that's one packet every two seconds, not two packets a second!)
// The server will start to enforce the limit after the first 5 packets have been received.
protected abstract int ShortDelay { get; init; }
private int ShortDelay
{
get
{
EnsureUsable();

return _shortDelay!.Value;
}
}

private int? _longDelay = null;

// From AniDB's wiki about UDP rate limiting:
// Long Term:
// A Client MUST NOT send more than one packet every four seconds over an extended amount of time.
// An extended amount of time is not defined. Use common sense.
protected abstract int LongDelay { get; init; }
private int LongDelay
{
get
{
EnsureUsable();

return _longDelay!.Value;
}
}

private long? _shortPeriod = null;

// Switch to longer delay after a short period
protected abstract long ShortPeriod { get; init; }
private long ShortPeriod
{
get
{
EnsureUsable();

return _shortPeriod!.Value;
}
}

private long? _resetPeriod = null;

// Switch to shorter delay after inactivity
protected abstract long ResetPeriod { get; init; }
private long ResetPeriod
{
get
{
EnsureUsable();

return _resetPeriod!.Value;
}
}

/// <summary>
/// Ensures that all the rate limiting values are usable.
/// </summary>
/// <param name="force">Force the values to be reapplied from settings, even if they are already in a usable state.</param>
private void EnsureUsable(bool force = false)
{
if (!force && _shortDelay.HasValue)
return;

protected AniDBRateLimiter(ILogger logger)
lock (_settingsLock)
{
if (!force && _shortDelay.HasValue)
return;

var settings = _settingsSelector(_settingsProvider.GetSettings());
var baseRate = settings.BaseRateInSeconds * 1000;
_shortDelay = baseRate;
_longDelay = baseRate * settings.SlowRateMultiplier;
_shortPeriod = baseRate * settings.SlowRatePeriodMultiplier;
_resetPeriod = baseRate * settings.ResetPeriodMultiplier;
}
}

protected AniDBRateLimiter(ILogger logger, ISettingsProvider settingsProvider, IShokoEventHandler eventHandler, Func<IServerSettings, AnidbRateLimitSettings> settingsSelector)
{
_logger = logger;
_requestWatch.Start();
_activeTimeWatch.Start();
_settingsProvider = settingsProvider;
_settingsSelector = settingsSelector;
_eventHandler = eventHandler;
_eventHandler.SettingsSaved += OnSettingsSaved;
}

~AniDBRateLimiter()
{
_eventHandler.SettingsSaved -= OnSettingsSaved;
}

private void OnSettingsSaved(object? sender, SettingsSavedEventArgs eventArgs)
{
// Reset the cached values when the settings are updated.
EnsureUsable(true);
}

private void ResetRate()
Expand Down
13 changes: 5 additions & 8 deletions Shoko.Server/Providers/AniDB/HTTP/HttpRateLimiter.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using Microsoft.Extensions.Logging;
using Shoko.Plugin.Abstractions;

using ISettingsProvider = Shoko.Server.Settings.ISettingsProvider;

namespace Shoko.Server.Providers.AniDB.HTTP;

public class HttpRateLimiter : AniDBRateLimiter
{
protected override int ShortDelay { get; init; } = 2_000;
protected override int LongDelay { get; init; } = 30_000;
protected override long ShortPeriod { get; init; } = 10_000;
protected override long ResetPeriod { get; init; } = 120_000;

public HttpRateLimiter(ILogger<HttpRateLimiter> logger) : base(logger)
{
}
public HttpRateLimiter(ILogger<HttpRateLimiter> logger, ISettingsProvider settingsProvider, IShokoEventHandler eventHandler)
: base(logger, settingsProvider, eventHandler, s => s.AniDb.HTTPRateLimit) { }
}
13 changes: 5 additions & 8 deletions Shoko.Server/Providers/AniDB/UDP/UDPRateLimiter.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using Microsoft.Extensions.Logging;
using Shoko.Plugin.Abstractions;

using ISettingsProvider = Shoko.Server.Settings.ISettingsProvider;

namespace Shoko.Server.Providers.AniDB.UDP;

public class UDPRateLimiter : AniDBRateLimiter
{
protected override int ShortDelay { get; init; } = 2_000;
protected override int LongDelay { get; init; } = 6_000;
protected override long ShortPeriod { get; init; } = 10_000;
protected override long ResetPeriod { get; init; } = 120_000;

public UDPRateLimiter(ILogger<UDPRateLimiter> logger) : base(logger)
{
}
public UDPRateLimiter(ILogger<UDPRateLimiter> logger, ISettingsProvider settingsProvider, IShokoEventHandler eventHandler)
: base(logger, settingsProvider, eventHandler, s => s.AniDb.UDPRateLimit) { }
}
4 changes: 4 additions & 0 deletions Shoko.Server/Settings/AniDbSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ public class AniDbSettings
public bool AutomaticallyImportSeries { get; set; } = false;

public AVDumpSettings AVDump { get; set; } = new();

public AnidbRateLimitSettings HTTPRateLimit { get; set; } = new();

public AnidbRateLimitSettings UDPRateLimit { get; set; } = new();
}
33 changes: 33 additions & 0 deletions Shoko.Server/Settings/AnidbRateLimitSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;

namespace Shoko.Server.Settings;

/// <summary>
/// Settings for rate limiting the Anidb provider.
/// </summary>
public class AnidbRateLimitSettings
{
/// <summary>
/// Base rate in seconds for request and the multipliers.
/// </summary>
[Range(2, 1000)]
public int BaseRateInSeconds { get; set; } = 2;

/// <summary>
/// Slow rate multiplier applied to the <seealso cref="BaseRateInSeconds"/>.
/// </summary>
[Range(2, 1000)]
public int SlowRateMultiplier { get; set; } = 3;

/// <summary>
/// Slow rate period multiplier applied to the <seealso cref="BaseRateInSeconds"/>.
/// </summary>
[Range(2, 1000)]
public int SlowRatePeriodMultiplier { get; set; } = 5;

/// <summary>
/// Reset period multiplier applied to the <seealso cref="BaseRateInSeconds"/>.
/// </summary>
[Range(2, 1000)]
public int ResetPeriodMultiplier { get; set; } = 60;
}

0 comments on commit 9cc0c51

Please sign in to comment.