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