diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
index b716f40f0..3797f9039 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
@@ -32,12 +32,21 @@ namespace MediaBrowser.Providers.Music
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
+ ///
+ /// 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
+ ///
private const long MusicBrainzQueryIntervalMs = 1050u;
+ ///
+ /// 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.
+ ///
+ private const uint MusicBrainzQueryAttempts = 5u;
+
public MusicBrainzAlbumProvider(
IHttpClient httpClient,
IApplicationHost appHost,
@@ -717,19 +726,12 @@ namespace MediaBrowser.Providers.Music
///
/// 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.
///
internal async Task 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
{
Url = MusicBrainzBaseUrl.TrimEnd('/') + url,
@@ -740,7 +742,38 @@ namespace MediaBrowser.Providers.Music
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;