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