|
@@ -32,12 +32,21 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
|
|
public readonly string MusicBrainzBaseUrl;
|
|
public readonly string MusicBrainzBaseUrl;
|
|
|
|
|
|
- // The Jellyfin user-agent is unrestricted but source IP must not exceed
|
|
|
|
- // one request per second, therefore we rate limit to avoid throttling.
|
|
|
|
- // Be prudent, use a value slightly above the minimun required.
|
|
|
|
- // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// The Jellyfin user-agent is unrestricted but source IP must not exceed
|
|
|
|
+ /// one request per second, therefore we rate limit to avoid throttling.
|
|
|
|
+ /// Be prudent, use a value slightly above the minimun required.
|
|
|
|
+ /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
|
|
|
|
+ /// </summary>
|
|
private const long MusicBrainzQueryIntervalMs = 1050u;
|
|
private const long MusicBrainzQueryIntervalMs = 1050u;
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// For each single MB lookup/search, this is the maximum number of
|
|
|
|
+ /// attempts that shall be made whilst receiving a 503 Server
|
|
|
|
+ /// Unavailable (indicating throttled) response.
|
|
|
|
+ /// </summary>
|
|
|
|
+ private const uint MusicBrainzQueryAttempts = 5u;
|
|
|
|
+
|
|
public MusicBrainzAlbumProvider(
|
|
public MusicBrainzAlbumProvider(
|
|
IHttpClient httpClient,
|
|
IHttpClient httpClient,
|
|
IApplicationHost appHost,
|
|
IApplicationHost appHost,
|
|
@@ -717,19 +726,12 @@ namespace MediaBrowser.Providers.Music
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Makes request to MusicBrainz server and awaits a response.
|
|
/// Makes request to MusicBrainz server and awaits a response.
|
|
|
|
+ /// A 503 Service Unavailable response indicates throttling to maintain a rate limit.
|
|
|
|
+ /// A number of retries shall be made in order to try and satisfy the request before
|
|
|
|
+ /// giving up and returning null.
|
|
/// </summary>
|
|
/// </summary>
|
|
internal async Task<HttpResponseInfo> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
|
|
internal async Task<HttpResponseInfo> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
|
|
{
|
|
{
|
|
- if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs)
|
|
|
|
- {
|
|
|
|
- // MusicBrainz is extremely adamant about limiting to one request per second
|
|
|
|
- var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
|
|
|
|
- await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- _logger.LogDebug("MusicBrainz time since previous request: {0}ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
|
|
|
|
- _stopWatchMusicBrainz.Restart();
|
|
|
|
-
|
|
|
|
var options = new HttpRequestOptions
|
|
var options = new HttpRequestOptions
|
|
{
|
|
{
|
|
Url = MusicBrainzBaseUrl.TrimEnd('/') + url,
|
|
Url = MusicBrainzBaseUrl.TrimEnd('/') + url,
|
|
@@ -740,7 +742,38 @@ namespace MediaBrowser.Providers.Music
|
|
BufferContent = false
|
|
BufferContent = false
|
|
};
|
|
};
|
|
|
|
|
|
- return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
|
|
|
|
|
|
+ HttpResponseInfo response;
|
|
|
|
+ var attempts = 0u;
|
|
|
|
+
|
|
|
|
+ do
|
|
|
|
+ {
|
|
|
|
+ attempts++;
|
|
|
|
+
|
|
|
|
+ if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs)
|
|
|
|
+ {
|
|
|
|
+ // MusicBrainz is extremely adamant about limiting to one request per second
|
|
|
|
+ var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
|
|
|
|
+ await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Write time since last request to debug log as evidence we're meeting rate limit
|
|
|
|
+ // requirement, before resetting stopwatch back to zero.
|
|
|
|
+ _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
|
|
|
|
+ _stopWatchMusicBrainz.Restart();
|
|
|
|
+
|
|
|
|
+ response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
|
|
|
|
+
|
|
|
|
+ // We retry a finite number of times, and only whilst MB is indcating 503 (throttling)
|
|
|
|
+ }
|
|
|
|
+ while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
|
|
|
|
+
|
|
|
|
+ // Log error if unable to query MB database due to throttling
|
|
|
|
+ if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable )
|
|
|
|
+ {
|
|
|
|
+ _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return response;
|
|
}
|
|
}
|
|
|
|
|
|
public int Order => 0;
|
|
public int Order => 0;
|