This library extends octokit.net, the well-known GitHub client, by enriching it with caching, resilient connections and logging capabilities. In fact, using this library your process won't halt in case of happening exceptions such as Rate Limit, Abuse, Http Exceptions and you will consume your rate limit more wisely.
Octokit.Extension adds a middleware to the Octokit's HttpClient and will try to resend the requests if anything goes wrong. There are some built-in policies that define how Octokit.Extensions should act to handle the exceptions. However, you can easily define your own custom policies to deal with exceptions based on your requirements.
This project hosts the awesome Polly at its heart. We are using Polly to define policies and to incorporate the retry behaviour into Octokit.Extensions. Using this library you get the following policies and behavior out of the box.
In case of happening HttpRequestException, the built-in policy tries to prevent the whole process from stopping by resending the request according to the following policy.
public Policy DefaultHttpRequestExceptionPolicy => Policy.Handle<HttpRequestException>()
.WaitAndRetryForeverAsync(sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timespan) =>
{
_logger?.LogInformation("A {exception} has occurred. Next try will happen in {time} seconds","HttpRequestException",timespan.TotalSeconds);
});
#Policies
All the default policies are implemented inside ResilientPolicies class.
In case of happening timout exceptions which been carrying in TaskCanceledException exception, the built-in policy tries to prevent the whole process from stopping by resending the request according to the following policy.
public Policy DefaultTimeoutExceptionPolicy => Policy.Handle<TaskCanceledException>(ex => !ex.CancellationToken.IsCancellationRequested)
.WaitAndRetryForeverAsync(sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timespan) =>
{
_logger?.LogInformation("A {exception} has occurred. Next try will happen in {time} seconds", "TaskCanceledException", timespan.TotalSeconds);
});
In case of reaching the Github's rate limit, octokit throws RateLimitExceededException exception. To deal with the situation, the built-in policy tries to prevent the whole process from stopping by waiting untill the rate limit window expires. The default implemented policy is as follows
public Policy DefaultRateLimitExceededExceptionPolicy => Policy.Handle<RateLimitExceededException>()
.RetryAsync(retryCount: 1,onRetryAsync: async (exception, retryCount) =>
{
var e = exception as RateLimitExceededException;
var sleepMilliseconds = (int)(e.Reset.ToLocalTime() - DateTime.Now).TotalMilliseconds;
_logger?.LogInformation("A {exception} has occurred. Next try will happen in {time} seconds", "RateLimitExceededException", sleepMilliseconds/1000);
await Task.Delay(sleepMilliseconds < 0 ? 10 : sleepMilliseconds + 1000).ConfigureAwait(false);
});
In case of abusing the Github policy, octokit throws AbuseException exception. To deal with the situation, the built-in policy tries to prevent the whole process from stopping by waiting untill the abuse time window expires. The default implemented policy is as follows
public Policy DefaultAbuseExceptionExceptionPolicy => Policy.Handle<AbuseException>()
.RetryAsync(retryCount: 1,onRetryAsync: async (exception, retryCount) =>
{
var e = exception as AbuseException;
var sleepMilliseconds = (int)TimeSpan.FromSeconds(e.RetryAfterSeconds.GetValueOrDefault(30)).TotalMilliseconds;
_logger?.LogInformation("A {exception} has occurred. Next try will happen in {time} seconds", "AbuseException", sleepMilliseconds / 1000);
await Task.Delay(sleepMilliseconds).ConfigureAwait(false);
});
You can easily opt-in your cache provider of choice by implementing ICacheProvider interface. Also, there is an in-memory built-in cache provider available to you.
var credentials = new Octokit.Credentials(token);
// with default in-memory caching
var client = new ResilientGitHubClientFactory().Create(new ProductHeaderValue(agentName), credentials,new InMemoryCacheProvider());
// without caching
var client = new ResilientGitHubClientFactory().Create(new ProductHeaderValue(agentName), credentials);
Integrating octokit.net.Extension in your source code is straightforward. In fact, instead of instantiating octokit's GithubClient via constructor, you just need to use the ResilientGitHubClientFactory which takes an optional ILogger to log the events.
var logger = new LoggerFactory()
.AddConsole().AddDebug()
.CreateLogger("Github.Octokit.Logger");
var credentials = new Octokit.Credentials(token);
// with logging
var client = new ResilientGitHubClientFactory(logger).Create(new ProductHeaderValue(agentName), credentials);
// withiut logging
var client = new ResilientGitHubClientFactory().Create(new ProductHeaderValue(agentName), credentials);
You are able to replace the built-in Polly policies with your own. In fact, ResilientGitHubClientFactory.Create takes params IAsyncPolicy[] as its last parameter. These policies define how we should act in case of happening any pre-defined catastrophic situation. If you don't pass any policies, Octokit.Extention will use the _DefaultResilientPolicies_ as its policies.
// using a custom policy.
var policy = Policy.Handle<HttpRequestException>().RetryAsync(2);
var client = new ResilientGitHubClientFactory().Create(new ProductHeaderValue(agentName),policy);
// using all the built-in policies automatically all-together
var client = new ResilientGitHubClientFactory().Create(new ProductHeaderValue(agentName));
// using built-in policies selectively
var builtinPolicies= new ResilientPolicies();
var client = new ResilientGitHubClientFactory().Create(new ProductHeaderValue(agentName),builtinPolicies.DefaultRateLimitExceededExceptionPolicy,
builtinPolicies.DefaultAbuseExceptionExceptionPolicy);
Install via Nuget
Install-Package Octokit.Extensions