-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds an initial implementation of a retry middleware, along with fleshing out the FixedCountRetryStrategy for compatibility with the new middleware. As of this commit, any Get/Set requests that fail with an Unavailable or Internal error gRPC status code will be retried up to 3 attempts. There is no backoff or jitter yet; we should implement that in the future. The determination of which gRPC status codes and request types are eligible for retries is implemented via a new IRetryEligibilityStrategy interface. This way, individual retry strategies have the ability to override the behavior, but in the 90% case they can just re-use the default eligibility strategy. NOTE: we implement retries as a bespoke middleware rather than using the retry configuration that is built into the .NET gRPC library because the built-in implementation has some strict restrictions that aren't compatible with our goals here (e.g. no response that includes any headers is eligible for retry in the built-in implementation, and we always get headers back from envoy.)
- Loading branch information
Showing
16 changed files
with
238 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
using Grpc.Core; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Momento.Sdk.Config.Retry | ||
{ | ||
public interface IRetryEligibilityStrategy | ||
{ | ||
public ILoggerFactory? LoggerFactory { get; } | ||
public IRetryEligibilityStrategy WithLoggerFactory(ILoggerFactory loggerFactory); | ||
public bool IsEligibleForRetry<TRequest>(Status status, TRequest request) where TRequest : class; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Grpc.Core; | ||
using Microsoft.Extensions.Logging; | ||
using Momento.Sdk.Config.Middleware; | ||
using System.Linq; | ||
using Momento.Protos.CacheClient; | ||
using System.Collections.Generic; | ||
|
||
namespace Momento.Sdk.Config.Retry | ||
{ | ||
internal class RetryMiddleware : IMiddleware | ||
{ | ||
public ILoggerFactory? LoggerFactory { get; } | ||
|
||
private readonly ILogger _logger; | ||
private readonly IRetryStrategy _retryStrategy; | ||
|
||
public RetryMiddleware(ILoggerFactory loggerFactory, IRetryStrategy retryStrategy) | ||
{ | ||
LoggerFactory = loggerFactory; | ||
_logger = loggerFactory.CreateLogger<RetryMiddleware>(); | ||
_retryStrategy = retryStrategy; | ||
} | ||
|
||
public RetryMiddleware WithLoggerFactory(ILoggerFactory loggerFactory) | ||
{ | ||
return new(loggerFactory, _retryStrategy); | ||
} | ||
|
||
IMiddleware IMiddleware.WithLoggerFactory(ILoggerFactory loggerFactory) | ||
{ | ||
return WithLoggerFactory(loggerFactory); | ||
} | ||
|
||
public async Task<MiddlewareResponseState<TResponse>> WrapRequest<TRequest, TResponse>( | ||
TRequest request, | ||
CallOptions callOptions, | ||
Func<TRequest, CallOptions, Task<MiddlewareResponseState<TResponse>>> continuation | ||
) where TRequest : class where TResponse : class | ||
{ | ||
var foo = request.GetType(); | ||
MiddlewareResponseState<TResponse> nextState; | ||
int attemptNumber = 0; | ||
int? retryAfterMillis = 0; | ||
do | ||
{ | ||
var delay = retryAfterMillis ?? 0; | ||
if (delay > 0) | ||
{ | ||
await Task.Delay(delay); | ||
} | ||
attemptNumber++; | ||
nextState = await continuation(request, callOptions); | ||
|
||
// NOTE: we need a try/catch block here, because: (a) we cannot call | ||
// `nextState.GetStatus()` until after we `await` the response, or | ||
// it will throw an error. and (b) if the status is anything other | ||
// than "ok", the `await` on the response will throw an exception. | ||
try | ||
{ | ||
await nextState.ResponseAsync; | ||
|
||
if (attemptNumber > 1) | ||
{ | ||
_logger.LogDebug($"Retry succeeded (attempt {attemptNumber})"); | ||
} | ||
break; | ||
} | ||
catch (Exception) | ||
{ | ||
var status = nextState.GetStatus(); | ||
_logger.LogDebug($"Request failed with status {status.StatusCode}, checking to see if we should retry; attempt Number: {attemptNumber}"); | ||
_logger.LogTrace($"Failed request status: {status}"); | ||
retryAfterMillis = _retryStrategy.DetermineWhenToRetryRequest(nextState.GetStatus(), request, attemptNumber); | ||
} | ||
} | ||
while (retryAfterMillis != null); | ||
|
||
return new MiddlewareResponseState<TResponse>( | ||
ResponseAsync: nextState.ResponseAsync, | ||
ResponseHeadersAsync: nextState.ResponseHeadersAsync, | ||
GetStatus: nextState.GetStatus, | ||
GetTrailers: nextState.GetTrailers | ||
); | ||
} | ||
} | ||
} | ||
|
Oops, something went wrong.