Skip to main content

layer_client/
retry.rs

1//! Retry policies for handling `FLOOD_WAIT` and transient I/O errors.
2
3use std::num::NonZeroU32;
4use std::ops::ControlFlow;
5use std::time::Duration;
6
7use crate::errors::InvocationError;
8
9/// Controls how the client reacts when an RPC call fails.
10pub trait RetryPolicy: Send + Sync + 'static {
11    fn should_retry(&self, ctx: &RetryContext) -> ControlFlow<(), Duration>;
12}
13
14/// Context passed to [`RetryPolicy::should_retry`] on each failure.
15pub struct RetryContext {
16    pub fail_count:   NonZeroU32,
17    pub slept_so_far: Duration,
18    pub error:        InvocationError,
19}
20
21/// Never retry.
22pub struct NoRetries;
23impl RetryPolicy for NoRetries {
24    fn should_retry(&self, _: &RetryContext) -> ControlFlow<(), Duration> {
25        ControlFlow::Break(())
26    }
27}
28
29/// Automatically sleep on FLOOD_WAIT and retry once on I/O errors.
30pub struct AutoSleep {
31    pub threshold:             Duration,
32    pub io_errors_as_flood_of: Option<Duration>,
33}
34
35impl Default for AutoSleep {
36    fn default() -> Self {
37        Self {
38            threshold:             Duration::from_secs(60),
39            io_errors_as_flood_of: Some(Duration::from_secs(1)),
40        }
41    }
42}
43
44impl RetryPolicy for AutoSleep {
45    fn should_retry(&self, ctx: &RetryContext) -> ControlFlow<(), Duration> {
46        if let Some(secs) = ctx.error.flood_wait_seconds() {
47            if ctx.fail_count.get() == 1 && secs <= self.threshold.as_secs() {
48                log::info!("FLOOD_WAIT_{secs} — sleeping before retry");
49                return ControlFlow::Continue(Duration::from_secs(secs));
50            }
51        }
52        if matches!(ctx.error, InvocationError::Io(_)) && ctx.fail_count.get() == 1 {
53            if let Some(d) = self.io_errors_as_flood_of {
54                log::info!("I/O error — sleeping {:?} before retry", d);
55                return ControlFlow::Continue(d);
56            }
57        }
58        ControlFlow::Break(())
59    }
60}