aws_smithy_types/timeout.rs
1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! This module defines types that describe timeouts that can be applied to various stages of the
7//! Smithy networking stack.
8
9use crate::config_bag::value::Value;
10use crate::config_bag::{ItemIter, Storable, Store, StoreReplace};
11use std::time::Duration;
12
13#[derive(Clone, Debug, Default, PartialEq, Copy)]
14enum CanDisable<T> {
15 Disabled,
16 #[default]
17 Unset,
18 Set(T),
19}
20
21impl<T> CanDisable<T> {
22 fn none_implies_disabled(value: Option<T>) -> Self {
23 match value {
24 Some(t) => CanDisable::Set(t),
25 None => CanDisable::Disabled,
26 }
27 }
28
29 fn is_some(&self) -> bool {
30 matches!(self, CanDisable::Set(_))
31 }
32
33 fn value(self) -> Option<T> {
34 match self {
35 CanDisable::Set(v) => Some(v),
36 _ => None,
37 }
38 }
39
40 fn merge_from_lower_priority(self, other: Self) -> Self {
41 match (self, other) {
42 // if we are unset. take the value from the other
43 (CanDisable::Unset, value) => value,
44 (us, _) => us,
45 }
46 }
47}
48
49impl<T> From<T> for CanDisable<T> {
50 fn from(value: T) -> Self {
51 Self::Set(value)
52 }
53}
54
55/// Builder for [`TimeoutConfig`].
56#[non_exhaustive]
57#[derive(Clone, Debug, Default)]
58pub struct TimeoutConfigBuilder {
59 connect_timeout: CanDisable<Duration>,
60 read_timeout: CanDisable<Duration>,
61 operation_timeout: CanDisable<Duration>,
62 operation_attempt_timeout: CanDisable<Duration>,
63}
64
65impl TimeoutConfigBuilder {
66 /// Creates a new builder with no timeouts set.
67 pub fn new() -> Self {
68 Default::default()
69 }
70
71 /// Sets the connect timeout.
72 ///
73 /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection.
74 pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
75 self.connect_timeout = connect_timeout.into();
76 self
77 }
78
79 /// Sets the connect timeout.
80 ///
81 /// If `None` is passed, this will explicitly disable the connection timeout.
82 ///
83 /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection.
84 pub fn set_connect_timeout(&mut self, connect_timeout: Option<Duration>) -> &mut Self {
85 self.connect_timeout = CanDisable::none_implies_disabled(connect_timeout);
86 self
87 }
88
89 /// Disables the connect timeout
90 pub fn disable_connect_timeout(mut self) -> Self {
91 self.connect_timeout = CanDisable::Disabled;
92 self
93 }
94
95 /// Sets the read timeout.
96 ///
97 /// The read timeout is the limit on the amount of time it takes to read the first byte of a response
98 /// from the time the request is initiated.
99 pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
100 self.read_timeout = read_timeout.into();
101 self
102 }
103
104 /// Sets the read timeout.
105 ///
106 /// If `None` is passed, this will explicitly disable the read timeout. To disable all timeouts use [`TimeoutConfig::disabled`].
107 ///
108 /// The read timeout is the limit on the amount of time it takes to read the first byte of a response
109 /// from the time the request is initiated.
110 pub fn set_read_timeout(&mut self, read_timeout: Option<Duration>) -> &mut Self {
111 self.read_timeout = CanDisable::none_implies_disabled(read_timeout);
112 self
113 }
114
115 /// Disables the read timeout
116 pub fn disable_read_timeout(mut self) -> Self {
117 self.read_timeout = CanDisable::Disabled;
118 self
119 }
120
121 /// Sets the operation timeout.
122 ///
123 /// An operation represents the full request/response lifecycle of a call to a service.
124 /// The operation timeout is a limit on the total amount of time it takes for an operation to be
125 /// fully serviced, including the time for all retries that may have been attempted for it.
126 ///
127 /// If you want to set a timeout on individual retry attempts, then see [`Self::operation_attempt_timeout`]
128 /// or [`Self::set_operation_attempt_timeout`].
129 pub fn operation_timeout(mut self, operation_timeout: Duration) -> Self {
130 self.operation_timeout = operation_timeout.into();
131 self
132 }
133
134 /// Sets the operation timeout.
135 ///
136 /// If `None` is passed, this will explicitly disable the read timeout. To disable all timeouts use [`TimeoutConfig::disabled`].
137 ///
138 /// An operation represents the full request/response lifecycle of a call to a service.
139 /// The operation timeout is a limit on the total amount of time it takes for an operation to be
140 /// fully serviced, including the time for all retries that may have been attempted for it.
141 ///
142 /// If you want to set a timeout on individual retry attempts, then see [`Self::operation_attempt_timeout`]
143 /// or [`Self::set_operation_attempt_timeout`].
144 pub fn set_operation_timeout(&mut self, operation_timeout: Option<Duration>) -> &mut Self {
145 self.operation_timeout = CanDisable::none_implies_disabled(operation_timeout);
146 self
147 }
148
149 /// Disables the operation timeout
150 pub fn disable_operation_timeout(mut self) -> Self {
151 self.operation_timeout = CanDisable::Disabled;
152 self
153 }
154
155 /// Sets the operation attempt timeout.
156 ///
157 /// An operation represents the full request/response lifecycle of a call to a service.
158 /// When retries are enabled, then this setting makes it possible to set a timeout for individual
159 /// retry attempts (including the initial attempt) for an operation.
160 ///
161 /// If you want to set a timeout on the total time for an entire request including all of its retries,
162 /// then see [`Self::operation_timeout`] /// or [`Self::set_operation_timeout`].
163 pub fn operation_attempt_timeout(mut self, operation_attempt_timeout: Duration) -> Self {
164 self.operation_attempt_timeout = operation_attempt_timeout.into();
165 self
166 }
167
168 /// Sets the operation attempt timeout.
169 ///
170 /// If `None` is passed, this will explicitly disable the operation timeout. To disable all timeouts use [`TimeoutConfig::disabled`].
171 ///
172 /// An operation represents the full request/response lifecycle of a call to a service.
173 /// When retries are enabled, then this setting makes it possible to set a timeout for individual
174 /// retry attempts (including the initial attempt) for an operation.
175 ///
176 /// If you want to set a timeout on individual retry attempts, then see [`Self::operation_attempt_timeout`]
177 /// or [`Self::set_operation_attempt_timeout`].
178 pub fn set_operation_attempt_timeout(
179 &mut self,
180 operation_attempt_timeout: Option<Duration>,
181 ) -> &mut Self {
182 self.operation_attempt_timeout =
183 CanDisable::none_implies_disabled(operation_attempt_timeout);
184 self
185 }
186
187 /// Disables the operation_attempt timeout
188 pub fn disable_operation_attempt_timeout(mut self) -> Self {
189 self.operation_attempt_timeout = CanDisable::Disabled;
190 self
191 }
192
193 /// Merges two timeout config builders together.
194 ///
195 /// Values from `other` will only be used as a fallback for values
196 /// from `self`. Useful for merging configs from different sources together when you want to
197 /// handle "precedence" per value instead of at the config level
198 ///
199 /// # Example
200 ///
201 /// ```rust
202 /// # use std::time::Duration;
203 /// # use aws_smithy_types::timeout::TimeoutConfig;
204 /// let a = TimeoutConfig::builder()
205 /// .connect_timeout(Duration::from_secs(3));
206 /// let b = TimeoutConfig::builder()
207 /// .connect_timeout(Duration::from_secs(5))
208 /// .operation_timeout(Duration::from_secs(3));
209 /// let timeout_config = a.take_unset_from(b).build();
210 ///
211 /// // A's value take precedence over B's value
212 /// assert_eq!(timeout_config.connect_timeout(), Some(Duration::from_secs(3)));
213 /// // A never set an operation timeout so B's value is used
214 /// assert_eq!(timeout_config.operation_timeout(), Some(Duration::from_secs(3)));
215 /// ```
216 pub fn take_unset_from(self, other: Self) -> Self {
217 Self {
218 connect_timeout: self
219 .connect_timeout
220 .merge_from_lower_priority(other.connect_timeout),
221 read_timeout: self
222 .read_timeout
223 .merge_from_lower_priority(other.read_timeout),
224 operation_timeout: self
225 .operation_timeout
226 .merge_from_lower_priority(other.operation_timeout),
227 operation_attempt_timeout: self
228 .operation_attempt_timeout
229 .merge_from_lower_priority(other.operation_attempt_timeout),
230 }
231 }
232
233 /// Builds a `TimeoutConfig`.
234 pub fn build(self) -> TimeoutConfig {
235 TimeoutConfig {
236 connect_timeout: self.connect_timeout,
237 read_timeout: self.read_timeout,
238 operation_timeout: self.operation_timeout,
239 operation_attempt_timeout: self.operation_attempt_timeout,
240 }
241 }
242}
243
244impl From<TimeoutConfig> for TimeoutConfigBuilder {
245 fn from(timeout_config: TimeoutConfig) -> Self {
246 TimeoutConfigBuilder {
247 connect_timeout: timeout_config.connect_timeout,
248 read_timeout: timeout_config.read_timeout,
249 operation_timeout: timeout_config.operation_timeout,
250 operation_attempt_timeout: timeout_config.operation_attempt_timeout,
251 }
252 }
253}
254
255/// Top-level configuration for timeouts
256///
257/// # Example
258///
259/// ```rust
260/// # use std::time::Duration;
261///
262/// # fn main() {
263/// use aws_smithy_types::timeout::TimeoutConfig;
264///
265/// let timeout_config = TimeoutConfig::builder()
266/// .operation_timeout(Duration::from_secs(30))
267/// .operation_attempt_timeout(Duration::from_secs(10))
268/// .connect_timeout(Duration::from_secs(3))
269/// .build();
270///
271/// assert_eq!(
272/// timeout_config.operation_timeout(),
273/// Some(Duration::from_secs(30))
274/// );
275/// assert_eq!(
276/// timeout_config.operation_attempt_timeout(),
277/// Some(Duration::from_secs(10))
278/// );
279/// assert_eq!(
280/// timeout_config.connect_timeout(),
281/// Some(Duration::from_secs(3))
282/// );
283/// # }
284/// ```
285#[non_exhaustive]
286#[derive(Clone, PartialEq, Debug)]
287pub struct TimeoutConfig {
288 connect_timeout: CanDisable<Duration>,
289 read_timeout: CanDisable<Duration>,
290 operation_timeout: CanDisable<Duration>,
291 operation_attempt_timeout: CanDisable<Duration>,
292}
293
294impl Storable for TimeoutConfig {
295 type Storer = StoreReplace<TimeoutConfig>;
296}
297
298/// Merger which merges timeout config settings when loading.
299///
300/// If no timeouts are set, `TimeoutConfig::disabled()` will be returned.
301///
302/// This API is not meant to be used externally.
303#[derive(Debug)]
304pub struct MergeTimeoutConfig;
305
306impl Storable for MergeTimeoutConfig {
307 type Storer = MergeTimeoutConfig;
308}
309impl Store for MergeTimeoutConfig {
310 type ReturnedType<'a> = TimeoutConfig;
311 type StoredType = <StoreReplace<TimeoutConfig> as Store>::StoredType;
312
313 fn merge_iter(iter: ItemIter<'_, Self>) -> Self::ReturnedType<'_> {
314 let mut result: Option<TimeoutConfig> = None;
315 // The item iterator iterates "backwards" over the config bags, starting at the highest
316 // priority layers and works backwards
317 for tc in iter {
318 match (result.as_mut(), tc) {
319 (Some(result), Value::Set(tc)) => {
320 // This maintains backwards compatible behavior where setting an EMPTY timeout config is equivalent to `TimeoutConfig::disabled()`
321 if result.has_timeouts() {
322 result.take_defaults_from(tc);
323 }
324 }
325 (None, Value::Set(tc)) => {
326 result = Some(tc.clone());
327 }
328 (_, Value::ExplicitlyUnset(_)) => result = Some(TimeoutConfig::disabled()),
329 }
330 }
331 result.unwrap_or(TimeoutConfig::disabled())
332 }
333}
334
335impl TimeoutConfig {
336 /// Returns a builder to create a `TimeoutConfig`.
337 pub fn builder() -> TimeoutConfigBuilder {
338 TimeoutConfigBuilder::new()
339 }
340
341 /// Returns a builder equivalent of this `TimeoutConfig`.
342 pub fn to_builder(&self) -> TimeoutConfigBuilder {
343 TimeoutConfigBuilder::from(self.clone())
344 }
345
346 /// Converts this `TimeoutConfig` into a builder.
347 pub fn into_builder(self) -> TimeoutConfigBuilder {
348 TimeoutConfigBuilder::from(self)
349 }
350
351 /// Fill any unfilled values in `self` from `other`.
352 pub fn take_defaults_from(&mut self, other: &TimeoutConfig) -> &mut Self {
353 self.connect_timeout = self
354 .connect_timeout
355 .merge_from_lower_priority(other.connect_timeout);
356 self.read_timeout = self
357 .read_timeout
358 .merge_from_lower_priority(other.read_timeout);
359 self.operation_timeout = self
360 .operation_timeout
361 .merge_from_lower_priority(other.operation_timeout);
362 self.operation_attempt_timeout = self
363 .operation_attempt_timeout
364 .merge_from_lower_priority(other.operation_attempt_timeout);
365 self
366 }
367
368 /// Returns a timeout config with all timeouts disabled.
369 pub fn disabled() -> TimeoutConfig {
370 TimeoutConfig {
371 connect_timeout: CanDisable::Disabled,
372 read_timeout: CanDisable::Disabled,
373 operation_timeout: CanDisable::Disabled,
374 operation_attempt_timeout: CanDisable::Disabled,
375 }
376 }
377
378 /// Returns this config's connect timeout.
379 ///
380 /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection.
381 pub fn connect_timeout(&self) -> Option<Duration> {
382 self.connect_timeout.value()
383 }
384
385 /// Returns this config's read timeout.
386 ///
387 /// The read timeout is the limit on the amount of time it takes to read the first byte of a response
388 /// from the time the request is initiated.
389 pub fn read_timeout(&self) -> Option<Duration> {
390 self.read_timeout.value()
391 }
392
393 /// Returns this config's operation timeout.
394 ///
395 /// An operation represents the full request/response lifecycle of a call to a service.
396 /// The operation timeout is a limit on the total amount of time it takes for an operation to be
397 /// fully serviced, including the time for all retries that may have been attempted for it.
398 pub fn operation_timeout(&self) -> Option<Duration> {
399 self.operation_timeout.value()
400 }
401
402 /// Returns this config's operation attempt timeout.
403 ///
404 /// An operation represents the full request/response lifecycle of a call to a service.
405 /// When retries are enabled, then this setting makes it possible to set a timeout for individual
406 /// retry attempts (including the initial attempt) for an operation.
407 pub fn operation_attempt_timeout(&self) -> Option<Duration> {
408 self.operation_attempt_timeout.value()
409 }
410
411 /// Returns true if any of the possible timeouts are set.
412 pub fn has_timeouts(&self) -> bool {
413 self.connect_timeout.is_some()
414 || self.read_timeout.is_some()
415 || self.operation_timeout.is_some()
416 || self.operation_attempt_timeout.is_some()
417 }
418}
419
420/// Configuration subset of [`TimeoutConfig`] for operation timeouts
421#[non_exhaustive]
422#[derive(Clone, PartialEq, Debug)]
423pub struct OperationTimeoutConfig {
424 operation_timeout: Option<Duration>,
425 operation_attempt_timeout: Option<Duration>,
426}
427
428impl OperationTimeoutConfig {
429 /// Returns this config's operation timeout.
430 ///
431 /// An operation represents the full request/response lifecycle of a call to a service.
432 /// The operation timeout is a limit on the total amount of time it takes for an operation to be
433 /// fully serviced, including the time for all retries that may have been attempted for it.
434 pub fn operation_timeout(&self) -> Option<Duration> {
435 self.operation_timeout
436 }
437
438 /// Returns this config's operation attempt timeout.
439 ///
440 /// An operation represents the full request/response lifecycle of a call to a service.
441 /// When retries are enabled, then this setting makes it possible to set a timeout for individual
442 /// retry attempts (including the initial attempt) for an operation.
443 pub fn operation_attempt_timeout(&self) -> Option<Duration> {
444 self.operation_attempt_timeout
445 }
446
447 /// Returns true if any of the possible timeouts are set.
448 pub fn has_timeouts(&self) -> bool {
449 self.operation_timeout.is_some() || self.operation_attempt_timeout.is_some()
450 }
451}
452
453impl From<&TimeoutConfig> for OperationTimeoutConfig {
454 fn from(cfg: &TimeoutConfig) -> Self {
455 OperationTimeoutConfig {
456 operation_timeout: cfg.operation_timeout.value(),
457 operation_attempt_timeout: cfg.operation_attempt_timeout.value(),
458 }
459 }
460}
461
462impl From<TimeoutConfig> for OperationTimeoutConfig {
463 fn from(cfg: TimeoutConfig) -> Self {
464 OperationTimeoutConfig::from(&cfg)
465 }
466}
467
468#[cfg(test)]
469mod test {
470 use crate::config_bag::{CloneableLayer, ConfigBag};
471 use crate::timeout::{MergeTimeoutConfig, TimeoutConfig};
472 use std::time::Duration;
473
474 #[test]
475 fn timeout_configs_merged_in_config_bag() {
476 let mut read_timeout = CloneableLayer::new("timeout");
477 read_timeout.store_put(
478 TimeoutConfig::builder()
479 .read_timeout(Duration::from_secs(3))
480 .connect_timeout(Duration::from_secs(1))
481 .build(),
482 );
483 let mut operation_timeout = CloneableLayer::new("timeout");
484 operation_timeout.store_put(
485 TimeoutConfig::builder()
486 .operation_timeout(Duration::from_secs(5))
487 .connect_timeout(Duration::from_secs(10))
488 .build(),
489 );
490 let cfg = ConfigBag::of_layers(vec![read_timeout.into(), operation_timeout.into()]);
491 let loaded = cfg.load::<MergeTimeoutConfig>();
492 // set by base layer
493 assert_eq!(loaded.read_timeout(), Some(Duration::from_secs(3)));
494
495 // set by higher layer
496 assert_eq!(loaded.operation_timeout(), Some(Duration::from_secs(5)));
497
498 // overridden by higher layer
499 assert_eq!(loaded.connect_timeout(), Some(Duration::from_secs(10)));
500 let mut next = cfg.add_layer("disabled");
501 next.interceptor_state()
502 .store_put(TimeoutConfig::disabled());
503
504 assert_eq!(next.load::<MergeTimeoutConfig>().read_timeout(), None);
505
506 // builder().build() acts equivalently to disabled
507 next.interceptor_state()
508 .store_put(TimeoutConfig::builder().build());
509 assert_eq!(next.load::<MergeTimeoutConfig>().read_timeout(), None);
510
511 // But if instead, you set a field of the timeout config, it will merge as expected.
512 next.interceptor_state().store_put(
513 TimeoutConfig::builder()
514 .operation_attempt_timeout(Duration::from_secs(1))
515 .build(),
516 );
517 assert_eq!(
518 next.load::<MergeTimeoutConfig>().read_timeout(),
519 Some(Duration::from_secs(3))
520 );
521 }
522}