protected override async Task <HttpResponseMessage> SendRequestAsync(IRequestInfo request, bool readBody) { var bucketId = GenerateBucketId(request); while (true) { await _rateLimiter.EnterLockAsync(bucketId, request.CancellationToken).ConfigureAwait(false); bool allowAnyStatus = request.AllowAnyStatusCode; ((RequestInfo)request).AllowAnyStatusCode = true; var response = await base.SendRequestAsync(request, readBody).ConfigureAwait(false); var info = new RateLimitInfo(response.Headers); if (response.IsSuccessStatusCode) { _rateLimiter.UpdateLimit(bucketId, info); return(response); } switch (response.StatusCode) { case (HttpStatusCode)429: _rateLimiter.UpdateLimit(bucketId, info); continue; case HttpStatusCode.BadGateway: //502 await Task.Delay(250, request.CancellationToken).ConfigureAwait(false); continue; default: if (allowAnyStatus) { return(response); } // TODO: Does this allocate? var bytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); if (bytes.Length > 0) { RestError error = null; try { error = _serializer.Read <RestError>(bytes.AsSpan()); } catch { } if (error != null) { throw new WumpusRestException(response.StatusCode, error.Code, error.Message); } Utf8String msg = null; try { msg = new Utf8String(bytes); } catch { } if (!(msg is null)) { throw new WumpusRestException(response.StatusCode, null, msg); } } throw new WumpusRestException(response.StatusCode); } } }
public virtual void UpdateLimit(string bucketId, RateLimitInfo info) { if (info.IsGlobal) { _globalWaitUntil = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value + (info.Lag?.TotalMilliseconds ?? 0.0)); } else { var bucket = _buckets.GetOrAdd(bucketId, x => new RequestBucket(this)); bucket.UpdateRateLimit(info); } }
public void UpdateRateLimit(RateLimitInfo info) { if (WindowCount == 0) { return; } lock (_lock) { bool hasQueuedReset = _resetsAt != null; if (info.Limit.HasValue && WindowCount != info.Limit.Value) { WindowCount = info.Limit.Value; _semaphore = info.Remaining.Value; } long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); DateTimeOffset?resetsAt = null; //Using X-RateLimit-Remaining causes a race condition /*if (info.Remaining.HasValue) * _semaphore = info.Remaining.Value;*/ if (info.RetryAfter.HasValue) //Millis { resetsAt = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); } else if (info.Reset.HasValue) //Secs { resetsAt = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0); } if (resetsAt == null) { WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) return; } if (!hasQueuedReset || resetsAt > _resetsAt) { _resetsAt = resetsAt; LastAttemptAt = resetsAt.Value; //Make sure we dont destroy this until after its been reset if (!hasQueuedReset) { int millis = (int)Math.Ceiling((_resetsAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); var _ = QueueReset(millis); } } } }