/// <summary>
        /// Initializes the <see cref="TokenBucketRateLimiter"/>.
        /// </summary>
        /// <param name="options">Options to specify the behavior of the <see cref="TokenBucketRateLimiter"/>.</param>
        public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options)
        {
            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (options.TokenLimit <= 0 || options.TokensPerPeriod <= 0)
            {
                throw new ArgumentException($"Both {nameof(options.TokenLimit)} and {nameof(options.TokensPerPeriod)} must be set to values greater than 0.", nameof(options));
            }
            if (options.QueueLimit < 0)
            {
                throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options));
            }
            if (options.ReplenishmentPeriod < TimeSpan.Zero)
            {
                throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options));
            }

            _options = new TokenBucketRateLimiterOptions
            {
                TokenLimit           = options.TokenLimit,
                QueueProcessingOrder = options.QueueProcessingOrder,
                QueueLimit           = options.QueueLimit,
                ReplenishmentPeriod  = options.ReplenishmentPeriod,
                TokensPerPeriod      = options.TokensPerPeriod,
                AutoReplenishment    = options.AutoReplenishment
            };

            _tokenCount = options.TokenLimit;

            _idleSince = _lastReplenishmentTick = Stopwatch.GetTimestamp();

            if (_options.AutoReplenishment)
            {
                _renewTimer = new Timer(Replenish, this, _options.ReplenishmentPeriod, _options.ReplenishmentPeriod);
            }
        }
 /// <summary>
 /// Initializes the <see cref="TokenBucketRateLimiter"/>.
 /// </summary>
 /// <param name="options">Options to specify the behavior of the <see cref="TokenBucketRateLimiter"/>.</param>
 public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options !!)
 {
Example #3
0
        // Used in tests that test behavior with specific time intervals
        internal void ReplenishInternal(uint nowTicks)
        {
            bool wrapped = false;

            // (uint)TickCount will wrap every ~50 days, we can detect that by checking if the new ticks is less than the last replenishment
            if (nowTicks < _lastReplenishmentTick)
            {
                wrapped = true;
            }

            // method is re-entrant (from Timer), lock to avoid multiple simultaneous replenishes
            lock (Lock)
            {
                // Fix the wrapping by using a long and adding uint.MaxValue in the wrapped case
                long nonWrappedTicks = wrapped ? (long)nowTicks + uint.MaxValue : nowTicks;
                if (nonWrappedTicks - _lastReplenishmentTick < _options.ReplenishmentPeriod.TotalMilliseconds)
                {
                    return;
                }

                _lastReplenishmentTick = nowTicks;

                int availablePermits = _tokenCount;
                TokenBucketRateLimiterOptions options = _options;
                int maxPermits = options.TokenLimit;
                int resourcesToAdd;

                if (availablePermits < maxPermits)
                {
                    resourcesToAdd = Math.Min(options.TokensPerPeriod, maxPermits - availablePermits);
                }
                else
                {
                    // All tokens available, nothing to do
                    return;
                }

                // Process queued requests
                Deque <RequestRegistration> queue = _queue;

                _tokenCount += resourcesToAdd;
                Debug.Assert(_tokenCount <= _options.TokenLimit);
                while (queue.Count > 0)
                {
                    RequestRegistration nextPendingRequest =
                        options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
                          ? queue.PeekHead()
                          : queue.PeekTail();

                    if (_tokenCount >= nextPendingRequest.Count)
                    {
                        // Request can be fulfilled
                        nextPendingRequest =
                            options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
                            ? queue.DequeueHead()
                            : queue.DequeueTail();

                        _queueCount -= nextPendingRequest.Count;
                        _tokenCount -= nextPendingRequest.Count;
                        Debug.Assert(_queueCount >= 0);
                        Debug.Assert(_tokenCount >= 0);

                        if (!nextPendingRequest.Tcs.TrySetResult(SuccessfulLease))
                        {
                            // Queued item was canceled so add count back
                            _tokenCount += nextPendingRequest.Count;
                        }
                        nextPendingRequest.CancellationTokenRegistration.Dispose();
                    }
                    else
                    {
                        // Request cannot be fulfilled
                        break;
                    }
                }
            }
        }