| @@ -47,7 +47,7 @@ namespace Discord.Net.Queue | |||||
| RequestQueueBucket bucket; | RequestQueueBucket bucket; | ||||
| bool success = FindBucket(ex.BucketId, out bucket); | bool success = FindBucket(ex.BucketId, out bucket); | ||||
| await _queue.RaiseRateLimitTriggered(ex.BucketId, success ? bucket.Definition : (Bucket)null, ex.RetryAfterMilliseconds).ConfigureAwait(false); | |||||
| await _queue.RaiseRateLimitTriggered(ex.BucketId, success ? bucket.Definition : null, ex.RetryAfterMilliseconds).ConfigureAwait(false); | |||||
| bucket.Pause(ex.RetryAfterMilliseconds); | bucket.Pause(ex.RetryAfterMilliseconds); | ||||
| } | } | ||||
| @@ -67,6 +67,7 @@ namespace Discord.Net.Queue | |||||
| //Get our 429 state | //Get our 429 state | ||||
| Task notifier; | Task notifier; | ||||
| int resumeTime; | int resumeTime; | ||||
| lock (_pauseLock) | lock (_pauseLock) | ||||
| { | { | ||||
| notifier = _resumeNotifier.Task; | notifier = _resumeNotifier.Task; | ||||
| @@ -133,14 +134,14 @@ namespace Discord.Net.Queue | |||||
| { | { | ||||
| _resumeNotifier = new TaskCompletionSource<byte>(); | _resumeNotifier = new TaskCompletionSource<byte>(); | ||||
| _pauseEndTick = unchecked(Environment.TickCount + milliseconds); | _pauseEndTick = unchecked(Environment.TickCount + milliseconds); | ||||
| QueueResumeAsync(milliseconds); | |||||
| QueueResumeAsync(_resumeNotifier, milliseconds); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private async Task QueueResumeAsync(int millis) | |||||
| private async Task QueueResumeAsync(TaskCompletionSource<byte> resumeNotifier, int millis) | |||||
| { | { | ||||
| await Task.Delay(millis).ConfigureAwait(false); | await Task.Delay(millis).ConfigureAwait(false); | ||||
| _resumeNotifier.SetResult(0); | |||||
| resumeNotifier.SetResult(0); | |||||
| } | } | ||||
| private async Task EnterAsync(int? endTick) | private async Task EnterAsync(int? endTick) | ||||
| @@ -151,8 +152,7 @@ namespace Discord.Net.Queue | |||||
| if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) | if (millis <= 0 || !await _semaphore.WaitAsync(millis).ConfigureAwait(false)) | ||||
| throw new TimeoutException(); | throw new TimeoutException(); | ||||
| } | } | ||||
| else | |||||
| await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
| await _semaphore.WaitAsync().ConfigureAwait(false); | |||||
| } | } | ||||
| private async Task QueueExitAsync() | private async Task QueueExitAsync() | ||||
| { | { | ||||
| @@ -7,9 +7,10 @@ namespace Discord.Net | |||||
| public string BucketId { get; } | public string BucketId { get; } | ||||
| public int RetryAfterMilliseconds { get; } | public int RetryAfterMilliseconds { get; } | ||||
| public HttpRateLimitException(int retryAfterMilliseconds) | |||||
| : base((HttpStatusCode)429) | |||||
| public HttpRateLimitException(string bucketId, int retryAfterMilliseconds, string reason) | |||||
| : base((HttpStatusCode)429, reason) | |||||
| { | { | ||||
| BucketId = bucketId; | |||||
| RetryAfterMilliseconds = retryAfterMilliseconds; | RetryAfterMilliseconds = retryAfterMilliseconds; | ||||
| } | } | ||||
| } | } | ||||
| @@ -124,25 +124,32 @@ namespace Discord.Net.Rest | |||||
| int statusCode = (int)response.StatusCode; | int statusCode = (int)response.StatusCode; | ||||
| if (statusCode < 200 || statusCode >= 300) //2xx = Success | if (statusCode < 200 || statusCode >= 300) //2xx = Success | ||||
| { | { | ||||
| if (statusCode == 429) | |||||
| { | |||||
| //TODO: Include bucket info | |||||
| int retryAfterMillis = int.Parse(response.Headers.GetValues("retry-after").First()); | |||||
| throw new HttpRateLimitException(retryAfterMillis); | |||||
| } | |||||
| string reason = null; | string reason = null; | ||||
| try | |||||
| JToken content = null; | |||||
| if (response.Content.Headers.GetValues("content-type").FirstOrDefault() == "application/json") | |||||
| { | { | ||||
| using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) | |||||
| using (var reader = new StreamReader(stream)) | |||||
| using (var json = new JsonTextReader(reader)) | |||||
| try | |||||
| { | { | ||||
| reason = (_errorDeserializer.Deserialize(json) as JToken).Value<string>("message"); | |||||
| using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) | |||||
| using (var reader = new StreamReader(stream)) | |||||
| using (var json = new JsonTextReader(reader)) | |||||
| { | |||||
| content = _errorDeserializer.Deserialize<JToken>(json); | |||||
| reason = content.Value<string>("message"); | |||||
| } | |||||
| } | } | ||||
| catch { } //Might have been HTML Should we check for content-type? | |||||
| } | |||||
| if (statusCode == 429 && content != null) | |||||
| { | |||||
| //TODO: Include bucket info | |||||
| string bucketId = content.Value<string>("bucket"); | |||||
| int retryAfterMillis = content.Value<int>("retry_after"); | |||||
| throw new HttpRateLimitException(bucketId, retryAfterMillis, reason); | |||||
| } | } | ||||
| catch { } //Might have been HTML | |||||
| throw new HttpException(response.StatusCode, reason); | |||||
| else | |||||
| throw new HttpException(response.StatusCode, reason); | |||||
| } | } | ||||
| if (headerOnly) | if (headerOnly) | ||||