/// <summary> /// Occurs when the server returns an error code of 500 or higher. /// </summary> /// <param name="e">Arguments for the event.</param> protected virtual void OnServerError(RetryEventArgs e) { ServerError?.Invoke(this, e); }
/// <summary> /// Occurs when a request fails because the rate limit has been exceeded (status code 429). /// </summary> /// <param name="e">Arguments for the event.</param> protected virtual void OnRateLimitExceeded(RetryEventArgs e) { RateLimitExceeded?.Invoke(this, e); }
/// <summary> /// Occurs when the client fails to connect to the server while executing a request. /// </summary> /// <param name="e">Arguments for the event.</param> protected virtual void OnConnectionFailed(RetryEventArgs e) { ConnectionFailed?.Invoke(this, e); }
/// <summary> /// Occurs when a request times out. /// </summary> /// <param name="e">Arguments for the event.</param> protected virtual void OnRequestTimedOut(RetryEventArgs e) { RequestTimedOut?.Invoke(this, e); }
/// <summary> /// Determines which action to take for the given response. /// </summary> /// <param name="response">An <see cref="HttpResponseMessage"/>.</param> /// <param name="attemptCount">The number of times the request has been attempted so far.</param> /// <param name="token">The cancellation token to cancel the operation.</param> /// <returns>A <see cref="ResponseAction"/>.</returns> protected virtual async Task <ResponseAction> DetermineResponseAction(RiotResponse response, int attemptCount, CancellationToken token) { if (response.TimedOut) { var args = new RetryEventArgs(response, attemptCount) { Retry = Settings.RetryOnTimeout }; OnRequestTimedOut(args); // Note: never retry if the token is cancelled. It will certainly fail the next time, too. if (args.Retry && !token.IsCancellationRequested) { return(ResponseAction.Retry); } if (Settings.ThrowOnError) { throw new RestTimeoutException(response, response.Exception); } return(ResponseAction.ReturnDefault); } if (response.Response == null) { var args = new RetryEventArgs(response, attemptCount) { Retry = Settings.RetryOnConnectionFailure }; OnConnectionFailed(args); if (args.Retry) { return(ResponseAction.Retry); } if (Settings.ThrowOnError) { throw new ConnectionFailedException(response, response.Exception); } return(ResponseAction.ReturnDefault); } var statusCode = (int)response.Response.StatusCode; if (statusCode == 429) { var args = new RetryEventArgs(response, attemptCount) { Retry = Settings.RetryOnRateLimitExceeded }; OnRateLimitExceeded(args); if (args.Retry) { return(ResponseAction.Retry); } if (Settings.ThrowOnError) { throw new RateLimitExceededException(response); } return(ResponseAction.ReturnDefault); } if (statusCode == 404) { OnResourceNotFound(new ResponseEventArgs(response)); if (Settings.ThrowOnNotFound) { throw new NotFoundException(response); } return(ResponseAction.ReturnDefault); } if (statusCode >= 500) { var args = new RetryEventArgs(response, attemptCount) { Retry = Settings.RetryOnServerError }; OnServerError(args); if (args.Retry) { return(ResponseAction.Retry); } if (Settings.ThrowOnError) { var message = await GetServerErrorMessage(response).ConfigureAwait(false); if (message != null) { throw new RestException(response, message); } else { throw new RestException(response); } } return(ResponseAction.ReturnDefault); } if (statusCode >= 400) { var message = await GetServerErrorMessage(response).ConfigureAwait(false); OnResponseError(new ResponseEventArgs(response, message)); if (Settings.ThrowOnError) { if (message != null) { throw new RestException(response, message, response.Exception); } else { throw new RestException(response, response.Exception); } } return(ResponseAction.ReturnDefault); } return(ResponseAction.Return); }
/// <summary> /// Sends a request. /// </summary> /// <param name="request">The request to send.</param> /// <param name="methodName">The name of the method being executed (for rate limiting purposes).</param> /// <param name="platformId">The platform ID corresponding to the server. This should equal one of the <see cref="Models.PlatformId"/> values.</param> /// <param name="token">The cancellation token to cancel the operation.</param> /// <returns>A task that represents the asynchronous operation.</returns> protected async Task <RiotResponse> SendAsync(HttpRequestMessage request, string methodName, string platformId, CancellationToken token) { DateTime targetTime = DateTime.UtcNow; if (retryAfterTimes.TryGetValue(platformId, out DateTime retryAfter) && retryAfter > targetTime) { targetTime = retryAfter; } if (rateLimiter != null) { var limiterDelayTime = rateLimiter.AddRequestOrGetDelay(methodName, platformId); if (limiterDelayTime > targetTime) { targetTime = limiterDelayTime; } } if (targetTime <= DateTime.UtcNow) { try { var response = await client.SendAsync(request, token).ConfigureAwait(false); return(new RiotResponse(response)); } catch (TaskCanceledException ex) { return(new RiotResponse(null, ex, true)); } catch (Exception ex) { return(new RiotResponse(null, ex)); } } else { // We need to throttle. Add requests to a queue to ensure that they are processed in order, so one request doesn't get starved. var args = new RetryEventArgs(null, 1) { Retry = Settings.RetryOnRateLimitExceeded }; OnRateLimitExceeded(args); if (!args.Retry) { if (Settings.ThrowOnError) { throw new RateLimitExceededException(); } return(null); } // Create a task to re-send but DON'T start it until targetTime. var task = new Task <Task <RiotResponse> >(() => SendAsync(request, methodName, platformId, token), token); ConcurrentQueue <Task <Task <RiotResponse> > > addedQueue = null; var queue = throttledRequestQueues.GetOrAdd(targetTime, (dt) => { return(addedQueue = new ConcurrentQueue <Task <Task <RiotResponse> > >()); }); queue.Enqueue(task); // Checking that addedQueue == queue is the only way to guarantee that the new queue was really added. // According to Microsoft docs: "If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times, // but its key/value pair might not be added to the dictionary for every call." if (addedQueue == queue) { ProcessRequestQueue(targetTime); } return(await await task); } }