static RestClient() { Version version = Assembly.Load(new AssemblyName("Discore")).GetName().Version; // Don't include revision since Discore uses the Major.Minor.Patch semantic. discoreVersion = $"{version.Major}.{version.Minor}.{version.Build}"; globalRateLimitLock = new RateLimitLock(); routeRateLimitLocks = new ConcurrentDictionary <string, RateLimitLock>(); }
/// <exception cref="DiscordHttpApiException"></exception> public async Task <DiscordApiData> Send(Func <HttpRequestMessage> requestCreate, string rateLimitRoute, CancellationToken?cancellationToken = null) { CancellationToken ct = cancellationToken ?? CancellationToken.None; // Get or create the rate limit lock for the specified route RateLimitLock routeLock; if (!routeRateLimitLocks.TryGetValue(rateLimitRoute, out routeLock)) { routeLock = new RateLimitLock(); routeRateLimitLocks[rateLimitRoute] = routeLock; } // Acquire route-specific lock using (await routeLock.LockAsync(ct).ConfigureAwait(false)) { HttpResponseMessage response; RateLimitHeaders rateLimitHeaders; bool retry = false; int attempts = 0; IDisposable globalLock = null; do { retry = false; attempts++; // If the route-specific lock requires a wait, delay before continuing await routeLock.WaitAsync(ct).ConfigureAwait(false); // If we don't already have the global lock and the global rate limit is active, acquire it. if (globalLock == null && globalRateLimitLock.RequiresWait) { globalLock = await globalRateLimitLock.LockAsync(ct).ConfigureAwait(false); } bool keepGlobalLock = false; try { // If we have the global lock, delay if it requires a wait if (globalLock != null) { await globalRateLimitLock.WaitAsync(ct).ConfigureAwait(false); } // Send request if (globalHttpClient != null) { response = await globalHttpClient.SendAsync(requestCreate(), ct).ConfigureAwait(false); } else { using (HttpClient http = CreateHttpClient()) { response = await http.SendAsync(requestCreate(), ct).ConfigureAwait(false); } } // Check rate limit headers rateLimitHeaders = RateLimitHeaders.ParseOrNull(response.Headers); if (rateLimitHeaders != null) { if ((int)response.StatusCode == 429) { // Tell the appropriate lock to wait if (rateLimitHeaders.IsGlobal) { globalRateLimitLock.ResetAfter(rateLimitHeaders.RetryAfter.Value); } else { routeLock.ResetAfter(rateLimitHeaders.RetryAfter.Value); } retry = RetryOnRateLimit && attempts < 20; // If we are retrying from a global rate limit, don't release the lock // so this request doesn't go to the back of the queue. keepGlobalLock = retry && rateLimitHeaders.IsGlobal; } else { // If the request succeeded but we are out of calls, set the route lock // to wait until the reset time. if (rateLimitHeaders.Remaining == 0) { routeLock.ResetAt(rateLimitHeaders.Reset); } } } // Check if the request received a bad gateway if (response.StatusCode == HttpStatusCode.BadGateway) { retry = attempts < 10; } // Release the global lock if necessary if (globalLock != null && !keepGlobalLock) { globalLock.Dispose(); } } catch { // Always release the global lock if we ran into an exception globalLock?.Dispose(); // Don't suppress the exception throw; } }while (retry); return(await ParseResponse(response, rateLimitHeaders).ConfigureAwait(false)); } }