1use core::fmt;
4
5use cloud_sdk_sanitization::sanitize_bytes;
6
7use super::policy::observe_monotonic;
8use super::{MonotonicDuration, MonotonicInstant};
9use crate::authentication::{AsyncAuthenticatedTransport, BlockingAuthenticatedTransport};
10use crate::operation::{CheckedResponseGuard, PreparedExecutionError, PreparedRequest};
11use crate::transport::BoundTransport;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum RetryPermitError {
16 TooEarly,
18 MonotonicRollback,
20 ElapsedBudgetExhausted,
22}
23
24impl_static_error!(RetryPermitError,
25 Self::TooEarly => "retry permit used before its authorized delay",
26 Self::MonotonicRollback => "retry permit monotonic observation moved backward",
27 Self::ElapsedBudgetExhausted => "retry permit elapsed budget is exhausted",
28);
29
30#[derive(Clone, Copy, Eq, PartialEq)]
32pub enum RetryExecutionError<E> {
33 Permit(RetryPermitError),
35 Execution(PreparedExecutionError<E>),
37}
38
39impl<E> fmt::Debug for RetryExecutionError<E> {
40 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Self::Permit(error) => formatter.debug_tuple("Permit").field(error).finish(),
43 Self::Execution(error) => formatter.debug_tuple("Execution").field(error).finish(),
44 }
45 }
46}
47
48impl<E> fmt::Display for RetryExecutionError<E> {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 formatter.write_str(match self {
51 Self::Permit(_) => "retry permit rejected execution",
52 Self::Execution(_) => "retry prepared execution failed",
53 })
54 }
55}
56
57impl<E: fmt::Debug> core::error::Error for RetryExecutionError<E> {}
58
59#[must_use]
100pub struct RetryPermit<'controller, 'request, 'subject> {
101 last_observed: &'controller mut MonotonicInstant,
102 prepared: &'subject PreparedRequest<'request>,
103 attempt: u16,
104 delay: MonotonicDuration,
105 not_before: MonotonicInstant,
106 started: MonotonicInstant,
107 max_elapsed: MonotonicDuration,
108}
109
110impl<'controller, 'request, 'subject> RetryPermit<'controller, 'request, 'subject> {
111 pub(crate) fn new(
112 last_observed: &'controller mut MonotonicInstant,
113 prepared: &'subject PreparedRequest<'request>,
114 attempt: u16,
115 delay: MonotonicDuration,
116 not_before: MonotonicInstant,
117 started: MonotonicInstant,
118 max_elapsed: MonotonicDuration,
119 ) -> Self {
120 Self {
121 last_observed,
122 prepared,
123 attempt,
124 delay,
125 not_before,
126 started,
127 max_elapsed,
128 }
129 }
130
131 #[must_use]
133 pub const fn attempt(&self) -> u16 {
134 self.attempt
135 }
136
137 #[must_use]
139 pub const fn delay(&self) -> MonotonicDuration {
140 self.delay
141 }
142
143 pub fn execute_blocking<'buffer, T>(
145 mut self,
146 now: MonotonicInstant,
147 transport: &T,
148 response_storage: &'buffer mut [u8],
149 response_header_storage: &'buffer mut [u8],
150 ) -> Result<CheckedResponseGuard<'buffer>, RetryExecutionError<T::Error>>
151 where
152 T: BlockingAuthenticatedTransport + BoundTransport,
153 {
154 sanitize_bytes(response_storage);
155 sanitize_bytes(response_header_storage);
156 self.authorize(now).map_err(RetryExecutionError::Permit)?;
157 self.prepared
158 .execute_blocking(transport, response_storage, response_header_storage)
159 .map_err(RetryExecutionError::Execution)
160 }
161
162 pub async fn execute_async<'transport, 'buffer, T>(
164 mut self,
165 now: MonotonicInstant,
166 transport: &'transport T,
167 response_storage: &'buffer mut [u8],
168 response_header_storage: &'buffer mut [u8],
169 ) -> Result<CheckedResponseGuard<'buffer>, RetryExecutionError<T::Error>>
170 where
171 T: AsyncAuthenticatedTransport + BoundTransport,
172 'request: 'transport,
173 'subject: 'transport,
174 {
175 sanitize_bytes(response_storage);
176 sanitize_bytes(response_header_storage);
177 self.authorize(now).map_err(RetryExecutionError::Permit)?;
178 self.prepared
179 .execute_async(transport, response_storage, response_header_storage)
180 .await
181 .map_err(RetryExecutionError::Execution)
182 }
183
184 fn authorize(&mut self, now: MonotonicInstant) -> Result<(), RetryPermitError> {
185 let elapsed = observe_monotonic(self.last_observed, self.started, now)
186 .map_err(|_| RetryPermitError::MonotonicRollback)?;
187 if now < self.not_before {
188 return Err(RetryPermitError::TooEarly);
189 }
190 if elapsed > self.max_elapsed {
191 return Err(RetryPermitError::ElapsedBudgetExhausted);
192 }
193 Ok(())
194 }
195}
196
197impl fmt::Debug for RetryPermit<'_, '_, '_> {
198 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
199 formatter
200 .debug_struct("RetryPermit")
201 .field("prepared", &"[bound request]")
202 .field("attempt", &self.attempt)
203 .field("delay", &self.delay)
204 .field("not_before", &self.not_before)
205 .field("started", &self.started)
206 .field("max_elapsed", &self.max_elapsed)
207 .finish()
208 }
209}