aws_smithy_runtime_api/client/
retries.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Retry handling and token bucket.
7//!
8//! This code defines when and how failed requests should be retried. It also defines the behavior
9//! used to limit the rate that requests are sent.
10
11pub mod classifiers;
12
13use crate::box_error::BoxError;
14use crate::client::interceptors::context::InterceptorContext;
15use crate::client::runtime_components::sealed::ValidateConfig;
16use crate::client::runtime_components::RuntimeComponents;
17use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
18use std::fmt;
19use std::sync::Arc;
20use std::time::Duration;
21
22use crate::impl_shared_conversions;
23pub use aws_smithy_types::retry::ErrorKind;
24#[cfg(feature = "test-util")]
25pub use test_util::AlwaysRetry;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28/// An answer to the question "should I make a request attempt?"
29pub enum ShouldAttempt {
30    /// Yes, an attempt should be made
31    Yes,
32    /// No, no attempt should be made
33    No,
34    /// Yes, an attempt should be made, but only after the given amount of time has passed
35    YesAfterDelay(Duration),
36}
37
38#[cfg(feature = "test-util")]
39impl ShouldAttempt {
40    /// Returns the delay duration if this is a `YesAfterDelay` variant.
41    pub fn expect_delay(self) -> Duration {
42        match self {
43            ShouldAttempt::YesAfterDelay(delay) => delay,
44            _ => panic!("Expected this to be the `YesAfterDelay` variant but it was the `{self:?}` variant instead"),
45        }
46    }
47
48    /// If this isn't a `No` variant, panic.
49    pub fn expect_no(self) {
50        if ShouldAttempt::No == self {
51            return;
52        }
53
54        panic!("Expected this to be the `No` variant but it was the `{self:?}` variant instead");
55    }
56}
57
58impl_shared_conversions!(convert SharedRetryStrategy from RetryStrategy using SharedRetryStrategy::new);
59
60/// Decider for whether or not to attempt a request, and when.
61///
62/// The orchestrator consults the retry strategy every time before making a request.
63/// This includes the initial request, and any retry attempts thereafter. The
64/// orchestrator will retry indefinitely (until success) if the retry strategy
65/// always returns `ShouldAttempt::Yes` from `should_attempt_retry`.
66pub trait RetryStrategy: Send + Sync + fmt::Debug {
67    /// Decides if the initial attempt should be made.
68    fn should_attempt_initial_request(
69        &self,
70        runtime_components: &RuntimeComponents,
71        cfg: &ConfigBag,
72    ) -> Result<ShouldAttempt, BoxError>;
73
74    /// Decides if a retry should be done.
75    ///
76    /// The previous attempt's output or error are provided in the
77    /// [`InterceptorContext`] when this is called.
78    ///
79    /// `ShouldAttempt::YesAfterDelay` can be used to add a backoff time.
80    fn should_attempt_retry(
81        &self,
82        context: &InterceptorContext,
83        runtime_components: &RuntimeComponents,
84        cfg: &ConfigBag,
85    ) -> Result<ShouldAttempt, BoxError>;
86}
87
88/// A shared retry strategy.
89#[derive(Clone, Debug)]
90pub struct SharedRetryStrategy(Arc<dyn RetryStrategy>);
91
92impl SharedRetryStrategy {
93    /// Creates a new [`SharedRetryStrategy`] from a retry strategy.
94    pub fn new(retry_strategy: impl RetryStrategy + 'static) -> Self {
95        Self(Arc::new(retry_strategy))
96    }
97}
98
99impl RetryStrategy for SharedRetryStrategy {
100    fn should_attempt_initial_request(
101        &self,
102        runtime_components: &RuntimeComponents,
103        cfg: &ConfigBag,
104    ) -> Result<ShouldAttempt, BoxError> {
105        self.0
106            .should_attempt_initial_request(runtime_components, cfg)
107    }
108
109    fn should_attempt_retry(
110        &self,
111        context: &InterceptorContext,
112        runtime_components: &RuntimeComponents,
113        cfg: &ConfigBag,
114    ) -> Result<ShouldAttempt, BoxError> {
115        self.0
116            .should_attempt_retry(context, runtime_components, cfg)
117    }
118}
119
120impl ValidateConfig for SharedRetryStrategy {}
121
122/// A type to track the number of requests sent by the orchestrator for a given operation.
123///
124/// `RequestAttempts` is added to the `ConfigBag` by the orchestrator,
125/// and holds the current attempt number.
126#[derive(Debug, Clone, Copy)]
127pub struct RequestAttempts {
128    attempts: u32,
129}
130
131impl RequestAttempts {
132    /// Creates a new [`RequestAttempts`] with the given number of attempts.
133    pub fn new(attempts: u32) -> Self {
134        Self { attempts }
135    }
136
137    /// Returns the number of attempts.
138    pub fn attempts(&self) -> u32 {
139        self.attempts
140    }
141}
142
143impl From<u32> for RequestAttempts {
144    fn from(attempts: u32) -> Self {
145        Self::new(attempts)
146    }
147}
148
149impl From<RequestAttempts> for u32 {
150    fn from(value: RequestAttempts) -> Self {
151        value.attempts()
152    }
153}
154
155impl Storable for RequestAttempts {
156    type Storer = StoreReplace<Self>;
157}
158
159#[cfg(feature = "test-util")]
160mod test_util {
161    use super::ErrorKind;
162    use crate::client::interceptors::context::InterceptorContext;
163    use crate::client::retries::classifiers::{ClassifyRetry, RetryAction};
164
165    /// A retry classifier for testing purposes. This classifier always returns
166    /// `Some(RetryAction::Error(ErrorKind))` where `ErrorKind` is the value provided when creating
167    /// this classifier.
168    #[derive(Debug)]
169    pub struct AlwaysRetry(pub ErrorKind);
170
171    impl ClassifyRetry for AlwaysRetry {
172        fn classify_retry(&self, error: &InterceptorContext) -> RetryAction {
173            tracing::debug!("Retrying error {:?} as an {:?}", error, self.0);
174            RetryAction::retryable_error(self.0)
175        }
176
177        fn name(&self) -> &'static str {
178            "Always Retry"
179        }
180    }
181}