// gRPC A6 retries and hedging as the pure decisions a channel asks after each
// attempt: whether to go again and how long to wait, what a server's pushback does,
// and how the throttle shared by one server's calls cuts retries off. The channel
// does the sending and the sleeping.
//
// moon run src/examples/28-retry-hedging
///|
fn wait(span : @moondate.Span?) -> String {
match span {
Some(s) => "again after \{s.nanos / 1_000_000L} ms"
None => "hand the reply back"
}
}
///|
fn run() -> Unit {
let unavailable = @moonrpc.Status::code(Unavailable)
let not_found = @moonrpc.Status::code(NotFound)
// A6's example policy: four attempts, 100 ms doubling to 1 s, a fifth either way.
// A roll of 0.5 sits mid-spread, so the waits are the schedule itself.
println("retry, UNAVAILABLE every time:")
let call = @moonrpc.Attempts::new(retry=@moonrpc.retry)
for _ in 0..<4 {
println(" attempt \{call.made()}: \{wait(call.next(unavailable, []))}")
}
let other = @moonrpc.Attempts::new(retry=@moonrpc.retry)
println("retry, NOT_FOUND: \{wait(other.next(not_found, []))}")
// The server can name the wait, or refuse any more.
let after : Array[@header.Header] = [
@header.Header::of("grpc-retry-pushback-ms", "750"),
]
let stop : Array[@header.Header] = [
@header.Header::of("grpc-retry-pushback-ms", "-1"),
]
let pushed = @moonrpc.Attempts::new(retry=@moonrpc.retry)
println("pushback 750: \{wait(pushed.next(unavailable, after))}")
println("then plain: \{wait(pushed.next(unavailable, []))}")
let refused = @moonrpc.Attempts::new(retry=@moonrpc.retry)
println("pushback -1: \{wait(refused.next(unavailable, stop))}")
// A stream the server refused goes again at once, policy or not, uncounted.
let bare = @moonrpc.Attempts::new()
println(
"refused stream: \{wait(bare.next(unavailable, [], unprocessed=true))}",
)
// Ten tokens, a tenth back per success: at five or fewer, nobody retries.
let throttle = @moonrpc.Throttle::new(
@moonrpc.Throttling::new(tokens=10, ratio=0.1),
)
for i in 0..<6 {
let c = @moonrpc.Attempts::new(retry=@moonrpc.retry, throttle~)
let next = wait(c.next(unavailable, []))
println("call \{i + 1} fails, \{throttle.tokens()} tokens left: \{next}")
}
// Hedging: the next attempt starts every 500 ms, or at once after a non-fatal
// status; anything else settles the call.
let hedge = @moonrpc.hedging
println("hedging, four attempts:")
println(" a fifth may start: \{hedge.more(4)}")
println(" UNAVAILABLE settles the call: \{hedge.commits(unavailable)}")
println(" NOT_FOUND settles the call: \{hedge.commits(not_found)}")
}
///|
#coverage.skip
fn main {
run()
}