Skip to main content

grammers_client/client/
retry_policy.rs

1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Retry policy trait and built-in policies for use in [`ClientConfiguration`].
10//!
11//! [`ClientConfiguration`]: crate::ClientConfiguration
12
13use std::num::NonZeroU32;
14use std::ops::ControlFlow;
15use std::time::Duration;
16
17use grammers_mtsender::{InvocationError, RpcError};
18
19/// This trait controls how the [`Client`] should behave when
20/// an invoked request fails with an [`InvocationError`].
21///
22/// [`Client`]: crate::Client
23pub trait RetryPolicy: Send + Sync {
24    /// Determines whether the failing request should retry.
25    ///
26    /// If it should Continue, a sleep duration before retrying is included.\
27    /// If it should Break, the context error will be propagated to the caller.
28    fn should_retry(&self, ctx: &RetryContext) -> ControlFlow<(), Duration>;
29}
30
31/// Context passed to [`RetryPolicy::should_retry`].
32pub struct RetryContext {
33    /// Amount of times the instance of this request has failed.
34    pub fail_count: NonZeroU32,
35    /// Sum of the durations for all previous continuations (not total time elapsed since first failure).
36    pub slept_so_far: Duration,
37    /// The most recent error caused by the instance of the request.
38    pub error: InvocationError,
39}
40
41/// Retry policy that will never retry.
42pub struct NoRetries;
43
44impl RetryPolicy for NoRetries {
45    fn should_retry(&self, _: &RetryContext) -> ControlFlow<(), Duration> {
46        ControlFlow::Break(())
47    }
48}
49
50/// Retry policy that will retry *once* on flood-wait and slow mode wait errors.
51///
52/// The library will sleep only if the duration to sleep for is below or equal to the threshold.
53pub struct AutoSleep {
54    /// The (inclusive) threshold below which the library should automatically sleep.
55    pub threshold: Duration,
56
57    /// `Some` if I/O errors should be treated as a flood error that would last the specified duration.
58    /// This duration will ignore the `threshold` and always be slept on on the first I/O error.
59    pub io_errors_as_flood_of: Option<Duration>,
60}
61
62impl RetryPolicy for AutoSleep {
63    fn should_retry(&self, ctx: &RetryContext) -> ControlFlow<(), Duration> {
64        match ctx.error {
65            InvocationError::Rpc(RpcError {
66                code: 420,
67                value: Some(seconds),
68                ..
69            }) if ctx.fail_count.get() == 1 && seconds as u64 <= self.threshold.as_secs() => {
70                ControlFlow::Continue(Duration::from_secs(seconds as _))
71            }
72            InvocationError::Io(_) if ctx.fail_count.get() == 1 => {
73                if let Some(duration) = self.io_errors_as_flood_of {
74                    ControlFlow::Continue(duration)
75                } else {
76                    ControlFlow::Break(())
77                }
78            }
79            _ => ControlFlow::Break(()),
80        }
81    }
82}
83
84impl Default for AutoSleep {
85    /// Returns an instance with a threshold of 60 seconds.
86    ///
87    /// I/O errors will be treated as if they were a 1-second flood.
88    fn default() -> Self {
89        Self {
90            threshold: Duration::from_secs(60),
91            io_errors_as_flood_of: Some(Duration::from_secs(1)),
92        }
93    }
94}