Author | SHA1 | Message | Date |
---|---|---|---|
|
0924f43db0
|
Merge branch 'dev' into feature/reset-fater | 5 years ago |
|
b4ae580d8d
|
patch: update Reset-After parsing precision
This patch applies the changes made to parsing precision in
|
5 years ago |
|
246d6a61e9
|
patch: wire new config properties to ApiClient | 5 years ago |
|
bda427a963
|
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. |
5 years ago |
@@ -152,5 +152,23 @@ namespace Discord | |||||
/// The currently set <see cref="RateLimitPrecision"/>. | /// The currently set <see cref="RateLimitPrecision"/>. | ||||
/// </returns> | /// </returns> | ||||
public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Millisecond; | public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Millisecond; | ||||
/// <summary> | |||||
/// Gets or sets whether or not rate-limits should use the system clock. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// If set to <c>false</c>, we will use the X-RateLimit-Reset-After header | |||||
/// to determine when a rate-limit expires, rather than comparing the | |||||
/// X-RateLimit-Reset timestamp to the system time. | |||||
/// | |||||
/// This should only be changed to false if the system is known to have | |||||
/// a clock that is out of sync. Relying on the Reset-After header will | |||||
/// incur network lag. | |||||
/// | |||||
/// Regardless of this property, we still rely on the system's wall-clock | |||||
/// to determine if a bucket is rate-limited; we do not use any monotonic | |||||
/// clock. Your system will still need a stable clock. | |||||
/// </remarks> | |||||
public bool UseSystemClock { get; set; } = true; | |||||
} | } | ||||
} | } |
@@ -44,6 +44,18 @@ namespace Discord | |||||
/// to all actions. | /// to all actions. | ||||
/// </remarks> | /// </remarks> | ||||
public string AuditLogReason { get; set; } | public string AuditLogReason { get; set; } | ||||
/// <summary> | |||||
/// Gets or sets whether or not this request should use the system | |||||
/// clock for rate-limiting. Defaults to <c>true</c>. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This property can also be set in <see cref="DiscordConfig">. | |||||
/// | |||||
/// On a per-request basis, the system clock should only be disabled | |||||
/// when millisecond precision is especially important, and the | |||||
/// hosting system is known to have a desynced clock. | |||||
/// </remarks> | |||||
public bool? UseSystemClock { get; set; } | |||||
internal bool IgnoreState { get; set; } | internal bool IgnoreState { get; set; } | ||||
internal string BucketId { get; set; } | internal string BucketId { get; set; } | ||||
@@ -46,18 +46,20 @@ namespace Discord.API | |||||
internal IRestClient RestClient { get; private set; } | internal IRestClient RestClient { get; private set; } | ||||
internal ulong? CurrentUserId { get; set; } | internal ulong? CurrentUserId { get; set; } | ||||
public RateLimitPrecision RateLimitPrecision { get; private set; } | public RateLimitPrecision RateLimitPrecision { get; private set; } | ||||
internal bool UseSystemClock { get; set; } | |||||
internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | 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; | _restClientProvider = restClientProvider; | ||||
UserAgent = userAgent; | UserAgent = userAgent; | ||||
DefaultRetryMode = defaultRetryMode; | DefaultRetryMode = defaultRetryMode; | ||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | ||||
RateLimitPrecision = rateLimitPrecision; | RateLimitPrecision = rateLimitPrecision; | ||||
UseSystemClock = useSystemClock; | |||||
RequestQueue = new RequestQueue(); | RequestQueue = new RequestQueue(); | ||||
_stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
@@ -265,6 +267,8 @@ namespace Discord.API | |||||
CheckState(); | CheckState(); | ||||
if (request.Options.RetryMode == null) | if (request.Options.RetryMode == null) | ||||
request.Options.RetryMode = DefaultRetryMode; | request.Options.RetryMode = DefaultRetryMode; | ||||
if (request.Options.UseSystemClock == null) | |||||
request.Options.UseSystemClock = UseSystemClock; | |||||
var stopwatch = Stopwatch.StartNew(); | var stopwatch = Stopwatch.StartNew(); | ||||
var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); | 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) { } | internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } | ||||
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) | 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) | internal override void Dispose(bool disposing) | ||||
{ | { | ||||
if (disposing) | if (disposing) | ||||
@@ -247,12 +247,19 @@ namespace Discord.Net.Queue | |||||
Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); | Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); | ||||
#endif | #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) | else if (info.Reset.HasValue) | ||||
{ | { | ||||
resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0); | resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0); | ||||
/* millisecond precision makes this unnecessary, retaining in case of regression | |||||
if (request.Options.IsReactionBucket) | if (request.Options.IsReactionBucket) | ||||
resetTick = DateTimeOffset.Now.AddMilliseconds(250); | resetTick = DateTimeOffset.Now.AddMilliseconds(250); | ||||
*/ | |||||
int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; | int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; | ||||
#if DEBUG_LIMITS | #if DEBUG_LIMITS | ||||
@@ -11,6 +11,7 @@ namespace Discord.Net | |||||
public int? Remaining { get; } | public int? Remaining { get; } | ||||
public int? RetryAfter { get; } | public int? RetryAfter { get; } | ||||
public DateTimeOffset? Reset { get; } | public DateTimeOffset? Reset { get; } | ||||
public TimeSpan? ResetAfter { get; } | |||||
public TimeSpan? Lag { get; } | public TimeSpan? Lag { get; } | ||||
internal RateLimitInfo(Dictionary<string, string> headers) | 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; | double.TryParse(temp, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var reset) ? DateTimeOffset.FromUnixTimeMilliseconds((long)(reset * 1000)) : (DateTimeOffset?)null; | ||||
RetryAfter = headers.TryGetValue("Retry-After", out temp) && | RetryAfter = headers.TryGetValue("Retry-After", out temp) && | ||||
int.TryParse(temp, NumberStyles.None, CultureInfo.InvariantCulture, out var retryAfter) ? retryAfter : (int?)null; | 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) && | Lag = headers.TryGetValue("Date", out temp) && | ||||
DateTimeOffset.TryParse(temp, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null; | DateTimeOffset.TryParse(temp, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null; | ||||
} | } | ||||
@@ -81,7 +81,8 @@ namespace Discord.WebSocket | |||||
: base(config, client) => BaseConfig = config; | : base(config, client) => BaseConfig = config; | ||||
private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | ||||
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | ||||
rateLimitPrecision: config.RateLimitPrecision); | |||||
rateLimitPrecision: config.RateLimitPrecision, | |||||
useSystemClock: config.UseSystemClock); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a Discord application information for the logged-in user. | /// Gets a Discord application information for the logged-in user. | ||||
@@ -39,8 +39,9 @@ namespace Discord.API | |||||
public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | ||||
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | ||||
RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second) | |||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision) | |||||
RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, | |||||
bool useSystemClock = true) | |||||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) | |||||
{ | { | ||||
_gatewayUrl = url; | _gatewayUrl = url; | ||||
if (url != null) | if (url != null) | ||||