13 13 | //! After Hyper accepts the request, the activation becomes an accepted claim.
|
14 14 | //! [`H2RequestBody`] owns the upload guard and [`H2ResponseBody`] owns the
|
15 15 | //! response guard. Either may finish first; the generation request count is
|
16 16 | //! released only after both finish. A returned request re-arms the existing
|
17 17 | //! body wrapper rather than nesting wrappers around the original body.
|
18 18 |
|
19 19 | use super::super::cell::h2::{
|
20 20 | H2Activation, H2CloseHandle, H2DispatchParts, H2ResponseGuard, H2UploadGuard,
|
21 21 | };
|
22 22 | use super::super::connection::{CloseReason, ConnectionState};
|
23 - | use super::{AcquisitionContext, H1HostHeaderInserted};
|
23 + | use super::{AcquisitionContext, FrozenConnectionSelection, H1HostHeaderInserted};
|
24 24 | use crate::client::downcast_error;
|
25 25 | use crate::sync::{Arc, Mutex};
|
26 26 | use aws_smithy_runtime_api::client::connection::CaptureSmithyConnection;
|
27 27 | use aws_smithy_runtime_api::client::result::ConnectorError;
|
28 28 | use aws_smithy_types::body::SdkBody;
|
29 29 | use http_1x::{Request, Response};
|
30 30 | use hyper::body::Body;
|
31 31 | use std::future::{poll_fn, Future};
|
32 32 | use std::pin::Pin;
|
33 33 | use std::task::{Context, Poll};
|
34 34 |
|
35 35 | /// Result of one checked HTTP/2 dispatch attempt.
|
36 36 | #[allow(
|
37 37 | clippy::large_enum_variant,
|
38 38 | reason = "boxing the successful response would allocate on every HTTP/2 request"
|
39 39 | )]
|
40 40 | pub(super) enum H2DispatchOutcome {
|
41 41 | /// Hyper accepted the request and produced a guarded response.
|
42 42 | Response(Response<SdkBody>),
|
43 43 | /// Hyper did not accept the request, so it may re-enter acquisition.
|
44 44 | NotAccepted(Box<H2UnacceptedRequest>),
|
45 45 | }
|
46 46 |
|
47 47 | /// Request and terminal fallback retained for one replacement selection.
|
48 48 | pub(super) struct H2UnacceptedRequest {
|
49 49 | /// Original request Hyper did not accept.
|
50 50 | request: Request<SdkBody>,
|
51 51 | /// Error returned if the request exhausts its replacement budget.
|
52 52 | terminal_error: ConnectorError,
|
53 53 | }
|
54 54 |
|
55 55 | impl H2UnacceptedRequest {
|
56 56 | /// Returns the original request and its terminal fallback.
|
57 57 | pub(super) fn into_parts(self: Box<Self>) -> (Request<SdkBody>, ConnectorError) {
|
58 58 | let Self {
|
59 59 | request,
|
60 60 | terminal_error,
|
61 61 | } = *self;
|
62 62 | (request, terminal_error)
|
63 63 | }
|
64 64 | }
|
65 65 |
|
66 66 | /// State retained after Hyper accepts one request.
|
67 67 | struct H2AcceptedDispatch {
|
68 68 | /// Connection retained through response creation.
|
69 69 | connection: Arc<ConnectionState>,
|
70 70 | /// Generation close authority used for connection-wide failures.
|
71 71 | close: H2CloseHandle,
|
72 72 | /// Metadata captured before the request moved into Hyper.
|
73 73 | captured_metadata: Option<aws_smithy_runtime_api::client::connection::ConnectionMetadata>,
|
74 74 | /// Response guard transferred to the guarded response body.
|
75 75 | response_guard: H2ResponseGuard,
|
76 76 | /// Whether the generation accepted an earlier request.
|
77 77 | reused: bool,
|
78 + | /// Selected-connection observation held until the request cannot return.
|
79 + | connection_selection: Option<FrozenConnectionSelection>,
|
78 80 | }
|
79 81 |
|
80 82 | /// Dispatches one request through prospective H2 request authority.
|
81 83 | ///
|
82 84 | /// Pool-side staleness reacquires before Hyper sees the request. Once Hyper
|
83 85 | /// returns an envelope, only a reused generation may reacquire.
|
84 86 | pub(super) async fn dispatch(
|
85 87 | context: &AcquisitionContext,
|
86 88 | mut request: Request<SdkBody>,
|
87 89 | mut activation: H2Activation,
|
88 90 | ) -> Result<H2DispatchOutcome, ConnectorError> {
|
89 91 | prepare_h2_request(&mut request, &context.absolute_uri);
|
90 92 | let connection = activation.connection().clone();
|
91 93 | let reused = activation.is_reused();
|
92 94 | let close = activation.close_handle();
|
93 - | let captured_metadata = request
|
95 + | let connection_capture = request
|
94 96 | .extensions()
|
95 97 | .get::<CaptureSmithyConnection>()
|
96 - | .cloned()
|
97 - | .map(|capture| {
|
98 - | let metadata = connection.info().h2_metadata(close.clone());
|
99 - | let captured = metadata.clone();
|
100 - | capture.set_connection_retriever(move || Some(captured.clone()));
|
101 - | metadata
|
102 - | });
|
98 + | .cloned();
|
99 + | let captured_metadata = (connection_capture.is_some()
|
100 + | || context.captures_connection_selection())
|
101 + | .then(|| connection.info().h2_metadata(close.clone()));
|
102 + | if let (Some(capture), Some(metadata)) = (connection_capture, &captured_metadata) {
|
103 + | let captured = metadata.clone();
|
104 + | capture.set_connection_retriever(move || Some(captured.clone()));
|
105 + | }
|
103 106 |
|
104 107 | let H2DispatchParts {
|
105 108 | mut sender,
|
106 109 | upload,
|
107 110 | response,
|
108 111 | } = activation.take_dispatch_parts();
|
109 112 | if sender.is_closed() {
|
110 113 | close.close(CloseReason::ProtocolClosed);
|
111 114 | let metadata = captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
112 115 | return resolve_unaccepted_request(
|
113 116 | request,
|
114 117 | reused,
|
115 118 | UnacceptedStage::BeforeHyper,
|
116 119 | ConnectorError::other(H2ConnectionClosedBeforeDispatch.into(), None)
|
117 120 | .with_connection(metadata),
|
118 121 | );
|
119 122 | }
|
120 123 | let Some(dispatch) = ConnectionState::try_commit_dispatch(&connection) else {
|
121 124 | let metadata = captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
122 125 | return resolve_unaccepted_request(
|
123 126 | request,
|
124 127 | reused,
|
125 128 | UnacceptedStage::BeforeHyper,
|
126 129 | ConnectorError::other(H2ConnectionClosedBeforeDispatch.into(), None)
|
127 130 | .with_connection(metadata),
|
128 131 | );
|
129 132 | };
|
130 133 |
|
131 134 | let body = H2RequestBodyHandle::arm(&mut request, upload);
|
132 135 |
|
133 136 | let mut send = Box::pin(sender.hyper_mut().try_send_request(request));
|
134 137 | let first = poll_fn(|cx| Poll::Ready(send.as_mut().poll(cx))).await;
|
135 138 |
|
136 139 | match first {
|
137 140 | Poll::Ready(Err(mut error)) if error.message().is_some() => {
|
138 141 | let returned = error
|
139 142 | .take_message()
|
140 143 | .expect("checked returned request disappeared");
|
141 144 | body.clear();
|
142 145 | drop(response);
|
143 146 | drop(dispatch);
|
144 147 | drop(activation);
|
145 148 | close.close(CloseReason::ProtocolClosed);
|
146 149 | let metadata =
|
147 150 | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
148 151 | resolve_unaccepted_request(
|
149 152 | returned,
|
150 153 | reused,
|
151 154 | UnacceptedStage::ReturnedByHyper,
|
152 155 | downcast_error(Box::new(error.into_error())).with_connection(metadata),
|
153 156 | )
|
154 157 | }
|
155 158 | Poll::Ready(result) => {
|
156 159 | activation.accept(dispatch);
|
160 + | let connection_selection =
|
161 + | context.freeze_connection_selection(reused, captured_metadata.as_ref());
|
157 162 | let sender_closed = sender.is_closed();
|
158 163 | resolve_h2_send(
|
159 164 | sender_closed,
|
160 165 | result,
|
161 166 | H2AcceptedDispatch {
|
162 167 | connection,
|
163 168 | close,
|
164 169 | captured_metadata,
|
165 170 | response_guard: response,
|
166 171 | reused,
|
172 + | connection_selection,
|
167 173 | },
|
168 174 | )
|
169 175 | }
|
170 176 | Poll::Pending => {
|
171 177 | activation.accept(dispatch);
|
178 + | let connection_selection =
|
179 + | context.freeze_connection_selection(reused, captured_metadata.as_ref());
|
172 180 | let result = send.await;
|
173 181 | let sender_closed = sender.is_closed();
|
174 182 | resolve_h2_send(
|
175 183 | sender_closed,
|
176 184 | result,
|
177 185 | H2AcceptedDispatch {
|
178 186 | connection,
|
179 187 | close,
|
180 188 | captured_metadata,
|
181 189 | response_guard: response,
|
182 190 | reused,
|
191 + | connection_selection,
|
183 192 | },
|
184 193 | )
|
185 194 | }
|
186 195 | }
|
187 196 | }
|
188 197 |
|
189 - | #[allow(
|
190 - | clippy::result_large_err,
|
191 - | reason = "ConnectorError preserves SDK classification and connection metadata"
|
192 - | )]
|
193 198 | fn resolve_h2_send(
|
194 199 | sender_closed: bool,
|
195 200 | result: Result<
|
196 201 | Response<hyper::body::Incoming>,
|
197 202 | hyper::client::conn::TrySendError<Request<SdkBody>>,
|
198 203 | >,
|
199 204 | accepted: H2AcceptedDispatch,
|
200 205 | ) -> Result<H2DispatchOutcome, ConnectorError> {
|
201 206 | let H2AcceptedDispatch {
|
202 207 | connection,
|
203 208 | close,
|
204 209 | captured_metadata,
|
205 210 | response_guard,
|
206 211 | reused,
|
212 + | connection_selection,
|
207 213 | } = accepted;
|
208 214 | match result {
|
209 215 | Ok(mut response) => {
|
216 + | if let Some(selection) = connection_selection {
|
217 + | selection.record();
|
218 + | }
|
210 219 | connection
|
211 220 | .info()
|
212 221 | .apply_connector_extras(response.extensions_mut());
|
213 222 | let (parts, body) = response.into_parts();
|
214 223 | let body = H2ResponseBody::new(body, response_guard);
|
215 224 | Ok(H2DispatchOutcome::Response(Response::from_parts(
|
216 225 | parts,
|
217 226 | SdkBody::from_body_1_x(body),
|
218 227 | )))
|
219 228 | }
|
220 229 | Err(mut error) => {
|
221 230 | // Hyper may return a queued envelope when its connection task
|
222 231 | // drops after the first poll. The returned request is the
|
223 232 | // authority for reacquisition; an error without it is terminal.
|
224 233 | if let Some(request) = error.take_message() {
|
225 234 | if let Some(body) = request.extensions().get::<H2RequestBodyHandle>() {
|
226 235 | body.clear();
|
227 236 | }
|
228 237 | drop(response_guard);
|
229 238 | close.close(CloseReason::ProtocolClosed);
|
230 239 | let metadata =
|
231 240 | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
232 241 | return resolve_unaccepted_request(
|
233 242 | request,
|
234 243 | reused,
|
235 244 | UnacceptedStage::ReturnedByHyper,
|
236 245 | downcast_error(Box::new(error.into_error())).with_connection(metadata),
|
237 246 | );
|
238 247 | }
|
239 248 | if sender_closed {
|
240 249 | close.close(CloseReason::ProtocolClosed);
|
241 250 | }
|
242 251 | drop(response_guard);
|
252 + | if let Some(selection) = connection_selection {
|
253 + | selection.record();
|
254 + | }
|
243 255 | let metadata =
|
244 256 | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
245 257 | Err(downcast_error(Box::new(error.into_error())).with_connection(metadata))
|
246 258 | }
|
247 259 | }
|
248 260 | }
|
249 261 |
|
250 262 | /// Restores the absolute URI and removes only a pool-synthesized H1 `Host`.
|
251 263 | fn prepare_h2_request(request: &mut Request<SdkBody>, absolute_uri: &http_1x::Uri) {
|
252 264 | *request.uri_mut() = absolute_uri.clone();
|
253 265 | if request
|
254 266 | .extensions_mut()
|
255 267 | .remove::<H1HostHeaderInserted>()
|
256 268 | .is_some()
|
257 269 | {
|
258 270 | request.headers_mut().remove(http_1x::header::HOST);
|
259 271 | }
|
260 272 | }
|
261 273 |
|
262 274 | /// Point at which the pool learned that Hyper had not accepted the request.
|
263 275 | #[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
264 276 | enum UnacceptedStage {
|
265 277 | /// The pool rejected a stale generation before calling Hyper.
|
266 278 | BeforeHyper,
|
267 279 | /// Hyper returned the original request envelope.
|
268 280 | ReturnedByHyper,
|
269 281 | }
|
270 282 |
|
271 283 | /// Applies retry authority to a request that Hyper did not accept.
|
272 284 | ///
|
273 285 | /// Pool-side checks always reacquire because no protocol code observed the
|
274 286 | /// request. A returned Hyper envelope reacquires only after prior successful
|
275 287 | /// use proves that replacing a stale pooled generation is appropriate.
|
276 - | #[allow(
|
277 - | clippy::result_large_err,
|
278 - | reason = "ConnectorError preserves SDK classification and connection metadata"
|
279 - | )]
|
280 288 | fn resolve_unaccepted_request(
|
281 289 | request: Request<SdkBody>,
|
282 290 | reused: bool,
|
283 291 | stage: UnacceptedStage,
|
284 292 | error: ConnectorError,
|
285 293 | ) -> Result<H2DispatchOutcome, ConnectorError> {
|
286 294 | if stage == UnacceptedStage::BeforeHyper || reused {
|
287 295 | Ok(H2DispatchOutcome::NotAccepted(Box::new(
|
288 296 | H2UnacceptedRequest {
|
289 297 | request,
|