aws_smithy_runtime/client/http/
connection_poisoning.rs1use crate::client::retries::classifiers::run_classifiers_on_ctx;
7use aws_smithy_runtime_api::box_error::BoxError;
8use aws_smithy_runtime_api::client::interceptors::context::{
9 AfterDeserializationInterceptorContextRef, BeforeTransmitInterceptorContextMut,
10};
11use aws_smithy_runtime_api::client::interceptors::{dyn_dispatch_hint, Intercept};
12use aws_smithy_runtime_api::client::retries::classifiers::RetryAction;
13use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
14use aws_smithy_types::config_bag::ConfigBag;
15use aws_smithy_types::retry::{ReconnectMode, RetryConfig, RetrySpec};
16use tracing::{debug, error};
17
18pub use aws_smithy_runtime_api::client::connection::CaptureSmithyConnection;
20
21#[non_exhaustive]
36#[derive(Debug, Default)]
37pub struct ConnectionPoisoningInterceptor {}
38
39impl ConnectionPoisoningInterceptor {
40 pub fn new() -> Self {
42 Self::default()
43 }
44}
45
46#[dyn_dispatch_hint]
47impl Intercept for ConnectionPoisoningInterceptor {
48 fn name(&self) -> &'static str {
49 "ConnectionPoisoningInterceptor"
50 }
51
52 fn modify_before_transmit(
53 &self,
54 context: &mut BeforeTransmitInterceptorContextMut<'_>,
55 _runtime_components: &RuntimeComponents,
56 cfg: &mut ConfigBag,
57 ) -> Result<(), BoxError> {
58 let capture_smithy_connection = CaptureSmithyConnection::new();
59 context
60 .request_mut()
61 .add_extension(capture_smithy_connection.clone());
62 cfg.interceptor_state().store_put(capture_smithy_connection);
63
64 Ok(())
65 }
66
67 fn read_after_deserialization(
68 &self,
69 context: &AfterDeserializationInterceptorContextRef<'_>,
70 runtime_components: &RuntimeComponents,
71 cfg: &mut ConfigBag,
72 ) -> Result<(), BoxError> {
73 let reconnect_mode = cfg
74 .load::<RetryConfig>()
75 .map(|rc| {
76 if rc
77 .retry_spec()
78 .is_some_and(|s| s.is_at_least(RetrySpec::V2_1))
79 {
80 ReconnectMode::ReuseAllConnections
81 } else {
82 rc.reconnect_mode()
83 }
84 })
85 .unwrap_or(ReconnectMode::ReconnectOnTransientError);
86 let captured_connection = cfg.load::<CaptureSmithyConnection>().cloned();
87 let retry_classifier_result =
88 run_classifiers_on_ctx(runtime_components.retry_classifiers(), context.inner());
89 let error_is_transient = retry_classifier_result == RetryAction::transient_error();
90 let connection_poisoning_is_enabled =
91 reconnect_mode == ReconnectMode::ReconnectOnTransientError;
92
93 if error_is_transient && connection_poisoning_is_enabled {
94 debug!("received a transient error, marking the connection for closure...");
95
96 if let Some(captured_connection) = captured_connection.and_then(|conn| conn.get()) {
97 captured_connection.poison();
98 debug!("the connection was marked for closure")
99 } else {
100 error!(
101 "unable to mark the connection for closure because no connection was found! The underlying HTTP connector never set a connection."
102 );
103 }
104 }
105
106 Ok(())
107 }
108}