feature: support X-RateLimit-Reset-After (#1372)
* feature: support X-RateLimit-Reset-After
Users may now optionally disable using the system clock to calculate
the ratelimit duration. This may be overrided globally, via
DiscordConfig, or per RequestOptions.
This change has been built and tested via the integrated test suite,
but has not been tested in the real world. Please verify this does not
break any of the edge-case ratelimits.
* patch: wire new config properties to ApiClient
* patch: update Reset-After parsing precision
This patch applies the changes made to parsing precision in 606dac3.
This commit is contained in:
@@ -46,18 +46,20 @@ namespace Discord.API
|
||||
internal IRestClient RestClient { get; private set; }
|
||||
internal ulong? CurrentUserId { get; set; }
|
||||
public RateLimitPrecision RateLimitPrecision { get; private set; }
|
||||
internal bool UseSystemClock { get; set; }
|
||||
|
||||
internal JsonSerializer Serializer => _serializer;
|
||||
|
||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
|
||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry,
|
||||
JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second)
|
||||
JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true)
|
||||
{
|
||||
_restClientProvider = restClientProvider;
|
||||
UserAgent = userAgent;
|
||||
DefaultRetryMode = defaultRetryMode;
|
||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||
RateLimitPrecision = rateLimitPrecision;
|
||||
UseSystemClock = useSystemClock;
|
||||
|
||||
RequestQueue = new RequestQueue();
|
||||
_stateLock = new SemaphoreSlim(1, 1);
|
||||
@@ -265,6 +267,8 @@ namespace Discord.API
|
||||
CheckState();
|
||||
if (request.Options.RetryMode == null)
|
||||
request.Options.RetryMode = DefaultRetryMode;
|
||||
if (request.Options.UseSystemClock == null)
|
||||
request.Options.UseSystemClock = UseSystemClock;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false);
|
||||
|
||||
@@ -28,7 +28,11 @@ namespace Discord.Rest
|
||||
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }
|
||||
|
||||
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
|
||||
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent);
|
||||
=> new API.DiscordRestApiClient(config.RestClientProvider,
|
||||
DiscordRestConfig.UserAgent,
|
||||
rateLimitPrecision: config.RateLimitPrecision,
|
||||
useSystemClock: config.UseSystemClock);
|
||||
|
||||
internal override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
||||
@@ -247,12 +247,19 @@ namespace Discord.Net.Queue
|
||||
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.Now.Add(info.ResetAfter.Value);
|
||||
}
|
||||
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
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Discord.Net
|
||||
public int? Remaining { get; }
|
||||
public int? RetryAfter { get; }
|
||||
public DateTimeOffset? Reset { get; }
|
||||
public TimeSpan? ResetAfter { get; }
|
||||
public TimeSpan? Lag { get; }
|
||||
|
||||
internal RateLimitInfo(Dictionary<string, string> headers)
|
||||
@@ -25,6 +26,8 @@ namespace Discord.Net
|
||||
double.TryParse(temp, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var reset) ? DateTimeOffset.FromUnixTimeMilliseconds((long)(reset * 1000)) : (DateTimeOffset?)null;
|
||||
RetryAfter = headers.TryGetValue("Retry-After", out temp) &&
|
||||
int.TryParse(temp, NumberStyles.None, CultureInfo.InvariantCulture, out var retryAfter) ? retryAfter : (int?)null;
|
||||
ResetAfter = headers.TryGetValue("X-RateLimit-Reset-After", out temp) &&
|
||||
float.TryParse(temp, out var resetAfter) ? TimeSpan.FromMilliseconds((long)(resetAfter * 1000)) : (TimeSpan?)null;
|
||||
Lag = headers.TryGetValue("Date", out temp) &&
|
||||
DateTimeOffset.TryParse(temp, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user