public async Task <Stream> SendAsync(IQueuedRequest request) { while (true) { try { return(await SendAsyncInternal(request).ConfigureAwait(false)); } catch (HttpRateLimitException ex) { //When a 429 occurs, we drop all our locks. //This is generally safe though since 429s actually occuring should be very rare. RequestQueueBucket bucket; bool success = FindBucket(ex.BucketId, out bucket); await _queue.RaiseRateLimitTriggered(ex.BucketId, success?bucket.Definition : null, ex.RetryAfterMilliseconds).ConfigureAwait(false); bucket.Pause(ex.RetryAfterMilliseconds); } } }
private async Task <Stream> SendAsyncInternal(IQueuedRequest request) { var endTick = request.TimeoutTick; //Wait until a spot is open in our bucket if (_semaphore != null) { await EnterAsync(endTick).ConfigureAwait(false); } try { while (true) { //Get our 429 state Task notifier; int resumeTime; lock (_pauseLock) { notifier = _resumeNotifier.Task; resumeTime = _pauseEndTick; } //Are we paused due to a 429? if (!notifier.IsCompleted) { //If the 429 ends after the maximum time for this request, timeout immediately if (endTick.HasValue && endTick.Value < resumeTime) { throw new TimeoutException(); } //Wait for the 429 to complete await notifier.ConfigureAwait(false); } try { //If there's a parent bucket, pass this request to them if (Parent != null) { return(await Parent.SendAsyncInternal(request).ConfigureAwait(false)); } //We have all our semaphores, send the request return(await request.SendAsync().ConfigureAwait(false)); } catch (HttpException ex) when(ex.StatusCode == HttpStatusCode.BadGateway) { continue; } } } finally { //Make sure we put this entry back after WindowMilliseconds if (_semaphore != null) { QueueExitAsync(); } } }