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); }
/// <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); } }
/// <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); } } }