1use std::num::NonZeroU32;
4use std::ops::ControlFlow;
5use std::time::Duration;
6
7use crate::errors::InvocationError;
8
9pub trait RetryPolicy: Send + Sync + 'static {
11 fn should_retry(&self, ctx: &RetryContext) -> ControlFlow<(), Duration>;
12}
13
14pub struct RetryContext {
16 pub fail_count: NonZeroU32,
17 pub slept_so_far: Duration,
18 pub error: InvocationError,
19}
20
21pub struct NoRetries;
23impl RetryPolicy for NoRetries {
24 fn should_retry(&self, _: &RetryContext) -> ControlFlow<(), Duration> {
25 ControlFlow::Break(())
26 }
27}
28
29pub 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}