retryInterceptor function
Builds a Connect Interceptor that retries failed unary calls per
RetryConfig. Without arguments, uses RFC 0006 defaults.
Composition order, outermost to innermost (per RFC 0002):
OTel -> Breaker -> Idempotency -> Retry -> Auth.
Implementation
Interceptor retryInterceptor([RetryConfig config = const RetryConfig()]) {
final jitter = config.jitterSource ?? defaultJitterSource;
final sleep = config.sleep ?? _realSleep;
final maxAttempts = math.max(2, config.maxAttempts);
final initial = config.initial.inMicroseconds <= 0
? const Duration(milliseconds: 100)
: config.initial;
final maxDelay = config.max.inMicroseconds < initial.inMicroseconds
? initial
: config.max;
final multiplier = math.max(1.0, config.multiplier);
final decorrelation = config.decorrelationFactor < 1.0
? 3.0
: config.decorrelationFactor;
return <I extends Object, O extends Object>(AnyFn<I, O> next) {
return (Request<I, O> req) async {
// Streaming is never retried: replaying a stream of inputs is not safe.
if (req.spec.streamType != StreamType.unary) {
return next(req);
}
// Idempotency gate: methods the schema does not annotate are skipped
// unless the caller explicitly opts in.
if (!config.allowNonIdempotent && req.spec.idempotency == null) {
return next(req);
}
var ceiling = initial;
var prev = initial;
var attempt = 0;
while (true) {
attempt++;
try {
return await next(req);
} on ConnectException catch (error) {
final isLast = attempt >= maxAttempts;
if (isLast || !_shouldRetry(error, config)) {
rethrow;
}
final delay = _nextDelay(
error: error,
ceiling: ceiling,
prev: prev,
config: config,
maxDelay: maxDelay,
initial: initial,
decorrelation: decorrelation,
jitter: jitter,
);
await sleep(delay);
prev = delay;
ceiling = _grow(ceiling, multiplier, maxDelay);
}
}
};
};
}