mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-10-15 15:40:52 +00:00
Fixed issues in DecayWindowTracker calculations
This commit is contained in:
@@ -18,9 +18,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
|
|
||||||
private int _currentWeight = 0;
|
private int _currentWeight = 0;
|
||||||
private DateTime _lastDecrease = DateTime.UtcNow;
|
private DateTime _lastDecrease = DateTime.UtcNow;
|
||||||
|
private double _decayProgress;
|
||||||
|
|
||||||
public DecayWindowTracker(int limit, TimeSpan period, double decayRate)
|
public DecayWindowTracker(
|
||||||
|
int limit,
|
||||||
|
TimeSpan period,
|
||||||
|
double decayRate)
|
||||||
{
|
{
|
||||||
|
if (period <= TimeSpan.Zero)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(period));
|
||||||
|
|
||||||
|
if (decayRate <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(decayRate));
|
||||||
Limit = limit;
|
Limit = limit;
|
||||||
TimePeriod = period;
|
TimePeriod = period;
|
||||||
DecreaseRate = decayRate;
|
DecreaseRate = decayRate;
|
||||||
@@ -31,83 +40,118 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
{
|
{
|
||||||
if (amount == null)
|
if (amount == null)
|
||||||
{
|
{
|
||||||
_lastDecrease = DateTime.UtcNow;
|
ResetState();
|
||||||
_currentWeight = 0;
|
return;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_currentWeight = Math.Max(0, _currentWeight - amount.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_currentWeight = Math.Max(0, _currentWeight - amount.Value);
|
||||||
|
if (_currentWeight == 0)
|
||||||
|
ResetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TimeSpan GetWaitTime(int weight, double allowedRateRatio)
|
public TimeSpan GetWaitTime(int weight, double allowedRateRatio)
|
||||||
{
|
{
|
||||||
// Decrease the counter based on the last update time and decay rate
|
var now = DateTime.UtcNow;
|
||||||
DecreaseCounter(DateTime.UtcNow);
|
DecreaseCounter(now);
|
||||||
|
|
||||||
if ((Current + weight) / (double)Limit > allowedRateRatio)
|
if ((Current + weight) / (double)Limit <= allowedRateRatio)
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
|
||||||
|
if (Current == 0)
|
||||||
{
|
{
|
||||||
// The weight would cause the rate limit to be passed
|
if (allowedRateRatio < 1)
|
||||||
if (Current == 0)
|
|
||||||
{
|
{
|
||||||
if (allowedRateRatio < 1)
|
throw new Exception(
|
||||||
{
|
"Request limit reached max utilization. " +
|
||||||
throw new Exception("Request limit reached max utilization. " +
|
"This request can never execute with the current rate limiter configuration. " +
|
||||||
$"This request can never execute with the current rate limiter configuration. Request weight: {weight}, RateLimit: {Limit}, " +
|
$"Request weight: {weight}, RateLimit: {Limit}, " +
|
||||||
$"Request ratio: {(Current + weight) / (double)Limit}, AllowedRateRatio: {allowedRateRatio}");
|
$"Request ratio: {(Current + weight) / (double)Limit}, " +
|
||||||
}
|
$"AllowedRateRatio: {allowedRateRatio}");
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Request limit reached without any prior request. " +
|
|
||||||
$"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the time to wait before this weight can be applied without going over the rate limit
|
throw new Exception(
|
||||||
return DetermineWaitTime(weight, allowedRateRatio);
|
"Request limit reached without any prior request. " +
|
||||||
|
"This request can never execute with the current rate limiter. " +
|
||||||
|
$"Request weight: {weight}, RateLimit: {Limit}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weight can fit without going over limit
|
return DetermineWaitTime(
|
||||||
return TimeSpan.Zero;
|
weight,
|
||||||
|
allowedRateRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ApplyWeight(int weight)
|
public void ApplyWeight(int weight)
|
||||||
{
|
{
|
||||||
if (_currentWeight == 0)
|
if (_currentWeight == 0)
|
||||||
|
{
|
||||||
_lastDecrease = DateTime.UtcNow;
|
_lastDecrease = DateTime.UtcNow;
|
||||||
|
_decayProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
_currentWeight += weight;
|
_currentWeight += weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void DecreaseCounter(DateTime now)
|
||||||
/// Decrease the counter based on time passed since last update and the decay rate
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="time"></param>
|
|
||||||
protected void DecreaseCounter(DateTime time)
|
|
||||||
{
|
{
|
||||||
var dif = (time - _lastDecrease).TotalMilliseconds / TimePeriod.TotalMilliseconds * DecreaseRate;
|
if (_currentWeight == 0)
|
||||||
var decrease = (int)Math.Floor(dif);
|
|
||||||
if (decrease >= 1)
|
|
||||||
{
|
{
|
||||||
_currentWeight = Math.Max(0, _currentWeight - (int)Math.Floor(dif));
|
_lastDecrease = now;
|
||||||
_lastDecrease = time;
|
_decayProgress = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var elapsed = now - _lastDecrease;
|
||||||
|
if (elapsed <= TimeSpan.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var elapsedDecay = elapsed.Ticks / (double)TimePeriod.Ticks * DecreaseRate;
|
||||||
|
|
||||||
|
var totalDecay = _decayProgress + elapsedDecay;
|
||||||
|
var completedDecay = (int)Math.Floor(totalDecay);
|
||||||
|
|
||||||
|
_lastDecrease = now;
|
||||||
|
|
||||||
|
if (completedDecay == 0)
|
||||||
|
{
|
||||||
|
_decayProgress = totalDecay;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentWeight = Math.Max(0, _currentWeight - completedDecay);
|
||||||
|
if (_currentWeight == 0)
|
||||||
|
{
|
||||||
|
// Decay cannot accumulate as credit while the counter is empty.
|
||||||
|
_decayProgress = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_decayProgress = totalDecay - completedDecay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private TimeSpan DetermineWaitTime(
|
||||||
/// Determine the time to wait before the weight would fit
|
int requestWeight,
|
||||||
/// </summary>
|
double allowedRateRatio)
|
||||||
/// <param name="requestWeight"></param>
|
|
||||||
/// <param name="allowedRateRatio"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private TimeSpan DetermineWaitTime(int requestWeight, double allowedRateRatio)
|
|
||||||
{
|
{
|
||||||
var weightToRemove = Math.Max(Current + requestWeight - Limit * allowedRateRatio, 0);
|
var weightToRemove = Current + requestWeight - Limit * allowedRateRatio;
|
||||||
var result = TimeSpan.FromMilliseconds(Math.Ceiling(weightToRemove / DecreaseRate) * TimePeriod.TotalMilliseconds);
|
|
||||||
if (result < TimeSpan.Zero)
|
// The counter is integer-valued, so enough whole weight units
|
||||||
return TimeSpan.Zero;
|
// must decay before the request can be admitted.
|
||||||
return result;
|
var requiredDecay = Math.Ceiling(weightToRemove);
|
||||||
|
var remainingDecay = Math.Max(0, requiredDecay - _decayProgress);
|
||||||
|
|
||||||
|
var waitTicks = Math.Ceiling(remainingDecay / DecreaseRate * TimePeriod.Ticks);
|
||||||
|
return waitTicks <= 0
|
||||||
|
? TimeSpan.Zero
|
||||||
|
: TimeSpan.FromTicks((long)waitTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetState()
|
||||||
|
{
|
||||||
|
_currentWeight = 0;
|
||||||
|
_decayProgress = 0;
|
||||||
|
_lastDecrease = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user