@ -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
/// <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 = 1050 u ;
/// <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 = 5 u ;
public MusicBrainzAlbumProvider (
IHttpClient httpClient ,
IApplicationHost appHost ,
@ -717,19 +726,12 @@ namespace MediaBrowser.Providers.Music
/// <summary>
/// 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>
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
{
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 = 0 u ;
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 ;