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}