예제 #1
0
        public RequestBucket(RequestQueue queue, RestRequest request, string id)
        {
            _queue = queue;
            Id     = id;

            _lock = new object();

            if (request.Options.IsClientBucket)
            {
                WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount;
            }
            else
            {
                WindowCount = 1; //Only allow one request until we get a header back
            }
            _semaphore    = WindowCount;
            _resetTick    = null;
            LastAttemptAt = DateTimeOffset.UtcNow;
        }
예제 #2
0
        private void UpdateRateLimit(int id, IRequest request, RateLimitInfo info, bool is429, bool redirected = false)
        {
            if (WindowCount == 0)
            {
                return;
            }

            lock (_lock)
            {
                if (redirected)
                {
                    Interlocked.Decrement(ref _semaphore); //we might still hit a real ratelimit if all tickets were already taken, can't do much about it since we didn't know they were the same
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Decrease Semaphore");
#endif
                }
                bool hasQueuedReset = _resetTick != null;

                if (info.Bucket != null && !redirected)
                {
                    (RequestBucket, BucketId)hashBucket = _queue.UpdateBucketHash(Id, info.Bucket);
                    if (!(hashBucket.Item1 is null) && !(hashBucket.Item2 is null))
                    {
                        if (hashBucket.Item1 == this) //this bucket got promoted to a hash queue
                        {
                            Id = hashBucket.Item2;
#if DEBUG_LIMITS
                            Debug.WriteLine($"[{id}] Promoted to Hash Bucket ({hashBucket.Item2})");
#endif
                        }
                        else
                        {
                            _redirectBucket = hashBucket.Item1;                                          //this request should be part of another bucket, this bucket will be disabled, redirect everything
                            _redirectBucket.UpdateRateLimit(id, request, info, is429, redirected: true); //update the hash bucket ratelimit
#if DEBUG_LIMITS
                            Debug.WriteLine($"[{id}] Redirected to {_redirectBucket.Id}");
#endif
                            return;
                        }
                    }
                }

                if (info.Limit.HasValue && WindowCount != info.Limit.Value)
                {
                    WindowCount = info.Limit.Value;
                    _semaphore  = info.Remaining.Value;
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}");
#endif
                }

                DateTimeOffset?resetTick = null;

                //Using X-RateLimit-Remaining causes a race condition

                /*if (info.Remaining.HasValue)
                 * {
                 *  Debug.WriteLine($"[{id}] X-RateLimit-Remaining: " + info.Remaining.Value);
                 *  _semaphore = info.Remaining.Value;
                 * }*/
                if (info.RetryAfter.HasValue)
                {
                    //RetryAfter is more accurate than Reset, where available
                    resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value);
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)");
#endif
                }
                else if (info.ResetAfter.HasValue && (request.Options.UseSystemClock.HasValue ? !request.Options.UseSystemClock.Value : false))
                {
                    resetTick = DateTimeOffset.UtcNow.Add(info.ResetAfter.Value);
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Reset-After: {info.ResetAfter.Value} ({info.ResetAfter?.TotalMilliseconds} ms)");
#endif
                }
                else if (info.Reset.HasValue)
                {
                    resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0);

                    /* millisecond precision makes this unnecessary, retaining in case of regression
                     * if (request.Options.IsReactionBucket)
                     *  resetTick = DateTimeOffset.Now.AddMilliseconds(250);
                     */

                    int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds;
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {info.Lag?.TotalMilliseconds} ms lag)");
#endif
                }
                else if (request.Options.IsClientBucket && Id != null)
                {
                    resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(Id).WindowSeconds);
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(Id).WindowSeconds * 1000} ms)");
#endif
                }
                else if (request.Options.IsGatewayBucket && request.Options.BucketId != null)
                {
                    resetTick = DateTimeOffset.UtcNow.AddSeconds(GatewayBucket.Get(request.Options.BucketId).WindowSeconds);
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Gateway Bucket ({GatewayBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)");
#endif
                    if (!hasQueuedReset)
                    {
                        _resetTick    = resetTick;
                        LastAttemptAt = resetTick.Value;
#if DEBUG_LIMITS
                        Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
#endif
                        var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds), request);
                    }
                    return;
                }

                if (resetTick == null)
                {
                    WindowCount = 0; //No rate limit info, disable limits on this bucket
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Disabled Semaphore");
#endif
                    return;
                }

                if (!hasQueuedReset || resetTick > _resetTick)
                {
                    _resetTick    = resetTick;
                    LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
#endif

                    if (!hasQueuedReset)
                    {
                        var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds), request);
                    }
                }
            }
        }
예제 #3
0
        private void UpdateRateLimit(int id, RestRequest request, RateLimitInfo info, bool is429)
        {
            if (WindowCount == 0)
            {
                return;
            }

            lock (_lock)
            {
                bool hasQueuedReset = _resetTick != null;
                if (info.Limit.HasValue && WindowCount != info.Limit.Value)
                {
                    WindowCount = info.Limit.Value;
                    _semaphore  = info.Remaining.Value;
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}");
#endif
                }

                var            now       = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                DateTimeOffset?resetTick = null;

                //Using X-RateLimit-Remaining causes a race condition

                /*if (info.Remaining.HasValue)
                 * {
                 *  Debug.WriteLine($"[{id}] X-RateLimit-Remaining: " + info.Remaining.Value);
                 *  _semaphore = info.Remaining.Value;
                 * }*/
                if (info.RetryAfter.HasValue)
                {
                    //RetryAfter is more accurate than Reset, where available
                    resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value);
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)");
#endif
                }
                else if (info.Reset.HasValue)
                {
                    resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0);

                    if (request.Options.IsReactionBucket)
                    {
                        resetTick = DateTimeOffset.Now.AddMilliseconds(250);
                    }

                    int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds;
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {info.Lag?.TotalMilliseconds} ms lag)");
#endif
                }
                else if (request.Options.IsClientBucket && request.Options.BucketId != null)
                {
                    resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId).WindowSeconds);
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)");
#endif
                }

                if (resetTick == null)
                {
                    WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token)
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Disabled Semaphore");
#endif
                    return;
                }

                if (!hasQueuedReset || resetTick > _resetTick)
                {
                    _resetTick    = resetTick;
                    LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset
#if DEBUG_LIMITS
                    Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms");
#endif

                    if (!hasQueuedReset)
                    {
                        var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds));
                    }
                }
            }
        }