// Retry policy and a retrying connection. A transport failure (a dropped connection or a
// closed socket) is transient and worth another attempt; a redis error reply is not, since
// the server answered. `backoff_delay` gives a transport the wait between attempts;
// `RetryConn` applies the policy in front of any connection.
///|
/// Whether an error on `attempt` (0-based, the attempt that just failed) is worth retrying
/// under `retry`: only a transport failure, and only while attempts remain.
pub fn should_retry(
error : RedisError,
attempt : Int32,
retry : RetryOptions,
) -> Bool {
if attempt + 1 >= retry.attempts {
return false
}
match error {
Connection(_) | Closed => true
_ => false
}
}
///|
/// A connection that retries transport failures up to `retry.attempts` times, leaving a
/// redis error reply to surface at once. A dropped connection stays dropped, so a retry that
/// is to reach the server needs `dial` to open another; `wait` sleeps for the backoff.
/// `@socket.connect_retrying` wires both.
pub struct RetryConn {
mut inner : &RedisConn
retry : RetryOptions
dial : (async () -> &RedisConn raise RedisError)?
wait : (async (Int64) -> Unit noraise)?
}
///|
/// A retrying view of `inner` under `retry`.
pub fn RetryConn::new(
inner : &RedisConn,
retry : RetryOptions,
dial? : async () -> &RedisConn raise RedisError,
wait? : async (Int64) -> Unit noraise,
) -> RetryConn {
{ inner, retry, dial, wait, }
}
///|
pub extend RetryConn with RedisConn::{execute, execute_all}
///|
pub impl RedisConn for RetryConn with fn execute(self, args : Array[Bytes]) -> @resp.RespValue raise RedisError {
for attempt = 0; ; attempt = attempt + 1 {
let failure : RedisError = try {
if attempt > 0 && self.dial is Some(dial) {
self.inner = dial()
}
return self.inner.execute(args)
} catch {
e => e
}
guard should_retry(failure, attempt, self.retry) else { raise failure }
if self.wait is Some(wait) {
wait(backoff_delay(attempt, self.retry))
}
}
}