Example #1
0
    public void DurationFromTokens(double perSecond, double durationSeconds, double tokens)
    {
        var limit = new Limit(perSecond);

        var durationFromTokens = limit.DurationFromTokens(tokens);

        Assert.Equal(TimeSpan.FromSeconds(durationSeconds), durationFromTokens);
    }
Example #2
0
    /// <summary>
    /// wait as an asynchronous operation.
    /// </summary>
    /// <param name="count">The count.</param>
    /// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
    /// <exception cref="Exception">rate: Wait(count={count}) exceeds limiter's burst {burst}.</exception>
    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
    public async Task WaitAsync(int count, CancellationToken cancellationToken)
    {
        // https://github.com/golang/time/blob/master/rate/rate.go#L226
        int   burst = default;
        Limit limit = default;

        lock (_sync)
        {
            burst = _burst;
            limit = _limit;
        }

        if (count > burst && limit != Limit.Max)
        {
            throw new Exception($"rate: Wait(count={count}) exceeds limiter's burst {burst}");
        }

        // Check if ctx is already cancelled
        cancellationToken.ThrowIfCancellationRequested();

        // Determine wait limit
        var waitLimit = limit.DurationFromTokens(count);

        while (true)
        {
            var now = _clock.UtcNow;
            var r   = ReserveImpl(now, count, waitLimit);
            if (r.Ok)
            {
                var delay = r.DelayFrom(now);
                if (delay > TimeSpan.Zero)
                {
                    await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
                }

                return;
            }

            await Task.Delay(waitLimit, cancellationToken).ConfigureAwait(false);
        }
    }
Example #3
0
    /// <summary>
    ///  reserveN is a helper method for AllowN, ReserveN, and WaitN.
    /// maxFutureReserve specifies the maximum reservation wait duration allowed.
    /// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN.
    /// </summary>
    /// <param name="now">The now.</param>
    /// <param name="number">The number.</param>
    /// <param name="maxFutureReserve">The maximum future reserve.</param>
    /// <returns>Reservation.</returns>
    private Reservation ReserveImpl(DateTimeOffset now, int number, TimeSpan maxFutureReserve)
    {
        lock (_sync)
        {
            if (_limit == Limit.Max)
            {
                return(new Reservation(
                           clock: _clock,
                           limiter: this,
                           ok: true,
                           tokens: number,
                           timeToAct: now));
            }

            var(newNow, last, tokens) = Advance(now);
            now = newNow;

            // Calculate the remaining number of tokens resulting from the request.
            tokens -= number;

            // Calculate the wait duration
            TimeSpan waitDuration = default;
            if (tokens < 0)
            {
                waitDuration = _limit.DurationFromTokens(-tokens);
            }

            // Decide result
            var ok = number <= _burst && waitDuration <= maxFutureReserve;

            // Prepare reservation
            if (ok)
            {
                var reservation = new Reservation(
                    clock: _clock,
                    limiter: this,
                    ok: true,
                    tokens: number,
                    limit: _limit,
                    timeToAct: now.Add(waitDuration));

                _last      = newNow;
                _tokens    = tokens;
                _lastEvent = reservation.TimeToAct;

                return(reservation);
            }
            else
            {
                var reservation = new Reservation(
                    clock: _clock,
                    limiter: this,
                    ok: false,
                    limit: _limit);

                _last = last;

                return(reservation);
            }
        }
    }