1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-10-27 06:02:33 +01:00

Generalized RateLimit logic to all indexers based on indexer id

Co-authored-by: Taloth Saldono <Taloth@users.noreply.github.com>
This commit is contained in:
Robin Dadswell 2021-03-07 23:28:37 +00:00 committed by GitHub
parent 914d3764dc
commit 10205da1c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 74 additions and 5 deletions

View File

@ -89,5 +89,38 @@ public void should_add_delay()
(GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(100)); (GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(100));
} }
[Test]
public void should_extend_subkey_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(300));
Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me-sub"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(400));
}
[Test]
public void should_honor_basekey_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(0));
Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me-sub"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(200));
}
[Test]
public void should_not_extend_basekey_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(100));
Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me"] - _epoch).Should().BeCloseTo(TimeSpan.FromMilliseconds(200));
}
} }
} }

View File

@ -109,7 +109,7 @@ private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieC
if (request.RateLimit != TimeSpan.Zero) if (request.RateLimit != TimeSpan.Zero)
{ {
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimit); _rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimitKey, request.RateLimit);
} }
_logger.Trace(request); _logger.Trace(request);

View File

@ -44,6 +44,7 @@ public HttpRequest(string url, HttpAccept httpAccept = null)
public bool StoreResponseCookie { get; set; } public bool StoreResponseCookie { get; set; }
public TimeSpan RequestTimeout { get; set; } public TimeSpan RequestTimeout { get; set; }
public TimeSpan RateLimit { get; set; } public TimeSpan RateLimit { get; set; }
public string RateLimitKey { get; set; }
public override string ToString() public override string ToString()
{ {

View File

@ -2,12 +2,14 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.TPL namespace NzbDrone.Common.TPL
{ {
public interface IRateLimitService public interface IRateLimitService
{ {
void WaitAndPulse(string key, TimeSpan interval); void WaitAndPulse(string key, TimeSpan interval);
void WaitAndPulse(string key, string subKey, TimeSpan interval);
} }
public class RateLimitService : IRateLimitService public class RateLimitService : IRateLimitService
@ -23,9 +25,37 @@ public RateLimitService(ICacheManager cacheManager, Logger logger)
public void WaitAndPulse(string key, TimeSpan interval) public void WaitAndPulse(string key, TimeSpan interval)
{ {
var waitUntil = _rateLimitStore.AddOrUpdate(key, WaitAndPulse(key, null, interval);
(s) => DateTime.UtcNow + interval, }
(s, i) => new DateTime(Math.Max(DateTime.UtcNow.Ticks, i.Ticks), DateTimeKind.Utc) + interval);
public void WaitAndPulse(string key, string subKey, TimeSpan interval)
{
var waitUntil = DateTime.UtcNow.Add(interval);
if (subKey.IsNotNullOrWhiteSpace())
{
// Expand the base key timer, but don't extend it beyond now+interval.
var baseUntil = _rateLimitStore.AddOrUpdate(key,
(s) => waitUntil,
(s, i) => new DateTime(Math.Max(waitUntil.Ticks, i.Ticks), DateTimeKind.Utc));
if (baseUntil > waitUntil)
{
waitUntil = baseUntil;
}
// Wait for the full key
var combinedKey = key + "-" + subKey;
waitUntil = _rateLimitStore.AddOrUpdate(combinedKey,
(s) => waitUntil,
(s, i) => new DateTime(Math.Max(waitUntil.Ticks, i.Add(interval).Ticks), DateTimeKind.Utc));
}
else
{
waitUntil = _rateLimitStore.AddOrUpdate(key,
(s) => waitUntil,
(s, i) => new DateTime(Math.Max(waitUntil.Ticks, i.Add(interval).Ticks), DateTimeKind.Utc));
}
waitUntil -= interval; waitUntil -= interval;

View File

@ -129,6 +129,7 @@ private string DownloadFromWebUrl(RemoteMovie remoteMovie, string torrentUrl)
try try
{ {
var request = new HttpRequest(torrentUrl); var request = new HttpRequest(torrentUrl);
request.RateLimitKey = remoteMovie?.Release?.IndexerId.ToString();
request.Headers.Accept = "application/x-bittorrent"; request.Headers.Accept = "application/x-bittorrent";
request.AllowAutoRedirect = false; request.AllowAutoRedirect = false;

View File

@ -44,7 +44,9 @@ public override string Download(RemoteMovie remoteMovie)
try try
{ {
nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData; var request = new HttpRequest(url);
request.RateLimitKey = remoteMovie?.Release?.IndexerId.ToString();
nzbData = _httpClient.Get(request).ResponseData;
_logger.Debug("Downloaded nzb for movie '{0}' finished ({1} bytes from {2})", remoteMovie.Release.Title, nzbData.Length, url); _logger.Debug("Downloaded nzb for movie '{0}' finished ({1} bytes from {2})", remoteMovie.Release.Title, nzbData.Length, url);
} }

View File

@ -301,6 +301,8 @@ protected virtual IndexerResponse FetchIndexerResponse(IndexerRequest request)
request.HttpRequest.RateLimit = RateLimit; request.HttpRequest.RateLimit = RateLimit;
} }
request.HttpRequest.RateLimitKey = Definition.Id.ToString();
request.HttpRequest.AllowAutoRedirect = true; request.HttpRequest.AllowAutoRedirect = true;
return new IndexerResponse(request, _httpClient.Execute(request.HttpRequest)); return new IndexerResponse(request, _httpClient.Execute(request.HttpRequest));