1 1 | /*
|
2 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 3 | * SPDX-License-Identifier: Apache-2.0
|
4 4 | */
|
5 5 |
|
6 6 | //! HTTP/2 dispatch and two-ended request completion.
|
7 7 | //!
|
8 - | //! [`H2Activation`] arrives with a prospective generation lease and a transient
|
8 + | //! [`H2Activation`] arrives with prospective request authority and a transient
|
9 9 | //! sender cloned from the connection-owning cell. Dispatch first checks sender
|
10 10 | //! and logical-connection state, then polls Hyper once. A request returned from
|
11 11 | //! that poll was not accepted and may re-enter protocol acquisition.
|
12 12 | //!
|
13 - | //! After Hyper accepts the request, the activation becomes an accepted lease.
|
14 - | //! [`H2RequestBody`] owns its send endpoint and [`H2ResponseBody`] owns its
|
15 - | //! receive endpoint. Either may finish first; the generation request count is
|
13 + | //! After Hyper accepts the request, the activation becomes an accepted claim.
|
14 + | //! [`H2RequestBody`] owns the upload guard and [`H2ResponseBody`] owns the
|
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 - | use super::super::cell::h2::{H2Activation, H2CloseHandle, H2DispatchParts, H2LeaseEndpoint};
|
20 - | use super::super::connection::ConnectionState;
|
21 - | use super::super::ConnectionPool;
|
19 + | use super::super::cell::h2::{
|
20 + | H2Activation, H2CloseHandle, H2DispatchParts, H2ResponseGuard, H2UploadGuard,
|
21 + | };
|
22 + | use super::super::connection::{CloseReason, ConnectionState};
|
22 23 | use super::{AcquisitionContext, H1HostHeaderInserted};
|
24 + | use crate::client::downcast_error;
|
23 25 | use crate::sync::{Arc, Mutex};
|
24 26 | use aws_smithy_runtime_api::client::connection::CaptureSmithyConnection;
|
25 27 | use aws_smithy_runtime_api::client::result::ConnectorError;
|
26 28 | use aws_smithy_types::body::SdkBody;
|
27 29 | use http_1x::{Request, Response};
|
28 30 | use hyper::body::Body;
|
29 31 | use std::future::{poll_fn, Future};
|
30 32 | use std::pin::Pin;
|
31 33 | use std::task::{Context, Poll};
|
32 34 |
|
33 35 | /// Result of one checked HTTP/2 dispatch attempt.
|
34 36 | #[allow(
|
35 37 | clippy::large_enum_variant,
|
36 38 | reason = "boxing the successful response would allocate on every HTTP/2 request"
|
37 39 | )]
|
38 - | pub(super) enum H2DispatchResult {
|
40 + | pub(super) enum H2DispatchOutcome {
|
39 41 | /// Hyper accepted the request and produced a guarded response.
|
40 42 | Response(Response<SdkBody>),
|
41 43 | /// Hyper did not accept the request, so it may re-enter acquisition.
|
42 - | Reacquire(Box<H2Reacquisition>),
|
44 + | NotAccepted(Box<H2UnacceptedRequest>),
|
43 45 | }
|
44 46 |
|
45 47 | /// Request and terminal fallback retained for one replacement selection.
|
46 - | pub(super) struct H2Reacquisition {
|
48 + | pub(super) struct H2UnacceptedRequest {
|
47 49 | /// Original request Hyper did not accept.
|
48 50 | request: Request<SdkBody>,
|
49 51 | /// Error returned if the request exhausts its replacement budget.
|
50 - | error: ConnectorError,
|
52 + | terminal_error: ConnectorError,
|
51 53 | }
|
52 54 |
|
53 - | impl H2Reacquisition {
|
55 + | impl H2UnacceptedRequest {
|
54 56 | /// Returns the original request and its terminal fallback.
|
55 57 | pub(super) fn into_parts(self: Box<Self>) -> (Request<SdkBody>, ConnectorError) {
|
56 - | let Self { request, error } = *self;
|
57 - | (request, error)
|
58 + | let Self {
|
59 + | request,
|
60 + | terminal_error,
|
61 + | } = *self;
|
62 + | (request, terminal_error)
|
58 63 | }
|
59 64 | }
|
60 65 |
|
61 66 | /// State retained after Hyper accepts one request.
|
62 67 | struct H2AcceptedDispatch {
|
63 68 | /// Connection retained through response creation.
|
64 69 | connection: Arc<ConnectionState>,
|
65 70 | /// Generation close authority used for connection-wide failures.
|
66 71 | close: H2CloseHandle,
|
67 72 | /// Metadata captured before the request moved into Hyper.
|
68 73 | captured_metadata: Option<aws_smithy_runtime_api::client::connection::ConnectionMetadata>,
|
69 - | /// Receive endpoint transferred to the guarded response body.
|
70 - | receive_endpoint: H2LeaseEndpoint,
|
74 + | /// Response guard transferred to the guarded response body.
|
75 + | response_guard: H2ResponseGuard,
|
71 76 | /// Whether the generation accepted an earlier request.
|
72 77 | reused: bool,
|
73 78 | }
|
74 79 |
|
75 - | impl ConnectionPool {
|
76 - | /// Dispatches one request through a prospective H2 generation lease.
|
77 - | ///
|
78 - | /// Pool-side staleness reacquires before Hyper sees the request. Once
|
79 - | /// Hyper returns an envelope, only a reused generation may reacquire.
|
80 - | pub(super) async fn dispatch_h2(
|
81 - | &self,
|
82 - | context: &AcquisitionContext,
|
83 - | mut request: Request<SdkBody>,
|
84 - | mut activation: H2Activation,
|
85 - | ) -> Result<H2DispatchResult, ConnectorError> {
|
86 - | prepare_h2_request(&mut request, &context.absolute_uri);
|
87 - | let connection = activation.connection().clone();
|
88 - | let reused = activation.is_reused();
|
89 - | let close = activation.close_handle();
|
90 - | let captured_metadata = request
|
91 - | .extensions()
|
92 - | .get::<CaptureSmithyConnection>()
|
93 - | .cloned()
|
94 - | .map(|capture| {
|
95 - | let metadata = connection.info().h2_metadata(close.clone());
|
96 - | let captured = metadata.clone();
|
97 - | capture.set_connection_retriever(move || Some(captured.clone()));
|
98 - | metadata
|
99 - | });
|
100 - |
|
101 - | let H2DispatchParts {
|
102 - | mut sender,
|
103 - | send_endpoint,
|
104 - | receive_endpoint,
|
105 - | } = activation.take_dispatch_parts();
|
106 - | if sender.is_closed() {
|
107 - | close.close(super::super::connection::CloseReason::ProtocolClosed);
|
80 + | /// Dispatches one request through prospective H2 request authority.
|
81 + | ///
|
82 + | /// Pool-side staleness reacquires before Hyper sees the request. Once Hyper
|
83 + | /// returns an envelope, only a reused generation may reacquire.
|
84 + | pub(super) async fn dispatch(
|
85 + | context: &AcquisitionContext,
|
86 + | mut request: Request<SdkBody>,
|
87 + | mut activation: H2Activation,
|
88 + | ) -> Result<H2DispatchOutcome, ConnectorError> {
|
89 + | prepare_h2_request(&mut request, &context.absolute_uri);
|
90 + | let connection = activation.connection().clone();
|
91 + | let reused = activation.is_reused();
|
92 + | let close = activation.close_handle();
|
93 + | let captured_metadata = request
|
94 + | .extensions()
|
95 + | .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 + | });
|
103 + |
|
104 + | let H2DispatchParts {
|
105 + | mut sender,
|
106 + | upload,
|
107 + | response,
|
108 + | } = activation.take_dispatch_parts();
|
109 + | if sender.is_closed() {
|
110 + | close.close(CloseReason::ProtocolClosed);
|
111 + | let metadata = captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
112 + | return resolve_unaccepted_request(
|
113 + | request,
|
114 + | reused,
|
115 + | UnacceptedStage::BeforeHyper,
|
116 + | ConnectorError::other(H2ConnectionClosedBeforeDispatch.into(), None)
|
117 + | .with_connection(metadata),
|
118 + | );
|
119 + | }
|
120 + | let Some(dispatch) = ConnectionState::try_commit_dispatch(&connection) else {
|
121 + | let metadata = captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
122 + | return resolve_unaccepted_request(
|
123 + | request,
|
124 + | reused,
|
125 + | UnacceptedStage::BeforeHyper,
|
126 + | ConnectorError::other(H2ConnectionClosedBeforeDispatch.into(), None)
|
127 + | .with_connection(metadata),
|
128 + | );
|
129 + | };
|
130 + |
|
131 + | let body = H2RequestBodyHandle::arm(&mut request, upload);
|
132 + |
|
133 + | let mut send = Box::pin(sender.hyper_mut().try_send_request(request));
|
134 + | let first = poll_fn(|cx| Poll::Ready(send.as_mut().poll(cx))).await;
|
135 + |
|
136 + | match first {
|
137 + | Poll::Ready(Err(mut error)) if error.message().is_some() => {
|
138 + | let returned = error
|
139 + | .take_message()
|
140 + | .expect("checked returned request disappeared");
|
141 + | body.clear();
|
142 + | drop(response);
|
143 + | drop(dispatch);
|
144 + | drop(activation);
|
145 + | close.close(CloseReason::ProtocolClosed);
|
108 146 | let metadata =
|
109 147 | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
110 - | return finish_unaccepted_request(
|
111 - | request,
|
148 + | resolve_unaccepted_request(
|
149 + | returned,
|
112 150 | reused,
|
113 - | UnacceptedStage::BeforeHyper,
|
114 - | ConnectorError::other(H2ConnectionClosedBeforeDispatch.into(), None)
|
115 - | .with_connection(metadata),
|
116 - | );
|
151 + | UnacceptedStage::ReturnedByHyper,
|
152 + | downcast_error(Box::new(error.into_error())).with_connection(metadata),
|
153 + | )
|
117 154 | }
|
118 - | let Some(dispatch) = ConnectionState::try_commit_dispatch(&connection) else {
|
119 - | let metadata =
|
120 - | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
121 - | return finish_unaccepted_request(
|
122 - | request,
|
123 - | reused,
|
124 - | UnacceptedStage::BeforeHyper,
|
125 - | ConnectorError::other(H2ConnectionClosedBeforeDispatch.into(), None)
|
126 - | .with_connection(metadata),
|
127 - | );
|
128 - | };
|
129 - |
|
130 - | let body = H2RequestBodyHandle::arm(&mut request, send_endpoint);
|
131 - |
|
132 - | let mut send = Box::pin(sender.hyper_mut().try_send_request(request));
|
133 - | let first = poll_fn(|cx| Poll::Ready(send.as_mut().poll(cx))).await;
|
134 - |
|
135 - | match first {
|
136 - | Poll::Ready(Err(mut error)) if error.message().is_some() => {
|
137 - | let returned = error
|
138 - | .take_message()
|
139 - | .expect("checked returned request disappeared");
|
140 - | body.clear();
|
141 - | drop(receive_endpoint);
|
142 - | drop(dispatch);
|
143 - | drop(activation);
|
144 - | close.close(super::super::connection::CloseReason::ProtocolClosed);
|
145 - | let metadata =
|
146 - | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
147 - | finish_unaccepted_request(
|
148 - | returned,
|
155 + | Poll::Ready(result) => {
|
156 + | activation.accept(dispatch);
|
157 + | let sender_closed = sender.is_closed();
|
158 + | resolve_h2_send(
|
159 + | sender_closed,
|
160 + | result,
|
161 + | H2AcceptedDispatch {
|
162 + | connection,
|
163 + | close,
|
164 + | captured_metadata,
|
165 + | response_guard: response,
|
149 166 | reused,
|
150 - | UnacceptedStage::ReturnedByHyper,
|
151 - | super::super::super::downcast_error(Box::new(error.into_error()))
|
152 - | .with_connection(metadata),
|
153 - | )
|
154 - | }
|
155 - | Poll::Ready(result) => {
|
156 - | activation.accept(dispatch);
|
157 - | let sender_closed = sender.is_closed();
|
158 - | self.finish_h2_send(
|
159 - | sender_closed,
|
160 - | result,
|
161 - | H2AcceptedDispatch {
|
162 - | connection,
|
163 - | close,
|
164 - | captured_metadata,
|
165 - | receive_endpoint,
|
166 - | reused,
|
167 - | },
|
168 - | )
|
169 - | }
|
170 - | Poll::Pending => {
|
171 - | activation.accept(dispatch);
|
172 - | let result = send.await;
|
173 - | let sender_closed = sender.is_closed();
|
174 - | self.finish_h2_send(
|
175 - | sender_closed,
|
176 - | result,
|
177 - | H2AcceptedDispatch {
|
178 - | connection,
|
179 - | close,
|
180 - | captured_metadata,
|
181 - | receive_endpoint,
|
182 - | reused,
|
183 - | },
|
184 - | )
|
185 - | }
|
167 + | },
|
168 + | )
|
169 + | }
|
170 + | Poll::Pending => {
|
171 + | activation.accept(dispatch);
|
172 + | let result = send.await;
|
173 + | let sender_closed = sender.is_closed();
|
174 + | resolve_h2_send(
|
175 + | sender_closed,
|
176 + | result,
|
177 + | H2AcceptedDispatch {
|
178 + | connection,
|
179 + | close,
|
180 + | captured_metadata,
|
181 + | response_guard: response,
|
182 + | reused,
|
183 + | },
|
184 + | )
|
186 185 | }
|
187 186 | }
|
187 + | }
|
188 188 |
|
189 - | #[allow(
|
190 - | clippy::result_large_err,
|
191 - | reason = "ConnectorError preserves SDK classification and connection metadata"
|
192 - | )]
|
193 - | fn finish_h2_send(
|
194 - | &self,
|
195 - | sender_closed: bool,
|
196 - | result: Result<
|
197 - | Response<hyper::body::Incoming>,
|
198 - | hyper::client::conn::TrySendError<Request<SdkBody>>,
|
199 - | >,
|
200 - | accepted: H2AcceptedDispatch,
|
201 - | ) -> Result<H2DispatchResult, ConnectorError> {
|
202 - | let H2AcceptedDispatch {
|
203 - | connection,
|
204 - | close,
|
205 - | captured_metadata,
|
206 - | receive_endpoint,
|
207 - | reused,
|
208 - | } = accepted;
|
209 - | match result {
|
210 - | Ok(mut response) => {
|
211 - | connection
|
212 - | .info()
|
213 - | .apply_connector_extras(response.extensions_mut());
|
214 - | let (parts, body) = response.into_parts();
|
215 - | let body = H2ResponseBody::new(body, receive_endpoint);
|
216 - | Ok(H2DispatchResult::Response(Response::from_parts(
|
217 - | parts,
|
218 - | SdkBody::from_body_1_x(body),
|
219 - | )))
|
220 - | }
|
221 - | Err(mut error) => {
|
222 - | // Hyper may return a queued envelope when its connection task
|
223 - | // drops after the first poll. The returned request is the
|
224 - | // authority for reacquisition; an error without it is terminal.
|
225 - | if let Some(request) = error.take_message() {
|
226 - | if let Some(body) = request.extensions().get::<H2RequestBodyHandle>() {
|
227 - | body.clear();
|
228 - | }
|
229 - | drop(receive_endpoint);
|
230 - | close.close(super::super::connection::CloseReason::ProtocolClosed);
|
231 - | let metadata =
|
232 - | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
233 - | return finish_unaccepted_request(
|
234 - | request,
|
235 - | reused,
|
236 - | UnacceptedStage::ReturnedByHyper,
|
237 - | super::super::super::downcast_error(Box::new(error.into_error()))
|
238 - | .with_connection(metadata),
|
239 - | );
|
240 - | }
|
241 - | if sender_closed {
|
242 - | close.close(super::super::connection::CloseReason::ProtocolClosed);
|
189 + | #[allow(
|
190 + | clippy::result_large_err,
|
191 + | reason = "ConnectorError preserves SDK classification and connection metadata"
|
192 + | )]
|
193 + | fn resolve_h2_send(
|
194 + | sender_closed: bool,
|
195 + | result: Result<
|
196 + | Response<hyper::body::Incoming>,
|
197 + | hyper::client::conn::TrySendError<Request<SdkBody>>,
|
198 + | >,
|
199 + | accepted: H2AcceptedDispatch,
|
200 + | ) -> Result<H2DispatchOutcome, ConnectorError> {
|
201 + | let H2AcceptedDispatch {
|
202 + | connection,
|
203 + | close,
|
204 + | captured_metadata,
|
205 + | response_guard,
|
206 + | reused,
|
207 + | } = accepted;
|
208 + | match result {
|
209 + | Ok(mut response) => {
|
210 + | connection
|
211 + | .info()
|
212 + | .apply_connector_extras(response.extensions_mut());
|
213 + | let (parts, body) = response.into_parts();
|
214 + | let body = H2ResponseBody::new(body, response_guard);
|
215 + | Ok(H2DispatchOutcome::Response(Response::from_parts(
|
216 + | parts,
|
217 + | SdkBody::from_body_1_x(body),
|
218 + | )))
|
219 + | }
|
220 + | Err(mut error) => {
|
221 + | // Hyper may return a queued envelope when its connection task
|
222 + | // drops after the first poll. The returned request is the
|
223 + | // authority for reacquisition; an error without it is terminal.
|
224 + | if let Some(request) = error.take_message() {
|
225 + | if let Some(body) = request.extensions().get::<H2RequestBodyHandle>() {
|
226 + | body.clear();
|
243 227 | }
|
244 - | drop(receive_endpoint);
|
228 + | drop(response_guard);
|
229 + | close.close(CloseReason::ProtocolClosed);
|
245 230 | let metadata =
|
246 231 | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
247 - | Err(
|
248 - | super::super::super::downcast_error(Box::new(error.into_error()))
|
249 - | .with_connection(metadata),
|
250 - | )
|
232 + | return resolve_unaccepted_request(
|
233 + | request,
|
234 + | reused,
|
235 + | UnacceptedStage::ReturnedByHyper,
|
236 + | downcast_error(Box::new(error.into_error())).with_connection(metadata),
|
237 + | );
|
238 + | }
|
239 + | if sender_closed {
|
240 + | close.close(CloseReason::ProtocolClosed);
|
251 241 | }
|
242 + | drop(response_guard);
|
243 + | let metadata =
|
244 + | captured_metadata.unwrap_or_else(|| connection.info().h2_metadata(close));
|
245 + | Err(downcast_error(Box::new(error.into_error())).with_connection(metadata))
|
252 246 | }
|
253 247 | }
|
254 248 | }
|
255 249 |
|
256 250 | /// Restores the absolute URI and removes only a pool-synthesized H1 `Host`.
|
257 251 | fn prepare_h2_request(request: &mut Request<SdkBody>, absolute_uri: &http_1x::Uri) {
|
258 252 | *request.uri_mut() = absolute_uri.clone();
|
259 253 | if request
|
260 254 | .extensions_mut()
|
261 255 | .remove::<H1HostHeaderInserted>()
|
262 256 | .is_some()
|
263 257 | {
|
264 258 | request.headers_mut().remove(http_1x::header::HOST);
|
265 259 | }
|
266 260 | }
|
267 261 |
|
268 262 | /// Point at which the pool learned that Hyper had not accepted the request.
|
269 263 | #[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
270 264 | enum UnacceptedStage {
|
271 265 | /// The pool rejected a stale generation before calling Hyper.
|
272 266 | BeforeHyper,
|
273 267 | /// Hyper returned the original request envelope.
|
274 268 | ReturnedByHyper,
|
275 269 | }
|
276 270 |
|
277 271 | /// Applies retry authority to a request that Hyper did not accept.
|
278 272 | ///
|
279 273 | /// Pool-side checks always reacquire because no protocol code observed the
|
280 274 | /// request. A returned Hyper envelope reacquires only after prior successful
|
281 275 | /// use proves that replacing a stale pooled generation is appropriate.
|
282 276 | #[allow(
|
283 277 | clippy::result_large_err,
|
284 278 | reason = "ConnectorError preserves SDK classification and connection metadata"
|
285 279 | )]
|
286 - | fn finish_unaccepted_request(
|
280 + | fn resolve_unaccepted_request(
|
287 281 | request: Request<SdkBody>,
|
288 282 | reused: bool,
|
289 283 | stage: UnacceptedStage,
|
290 284 | error: ConnectorError,
|
291 - | ) -> Result<H2DispatchResult, ConnectorError> {
|
285 + | ) -> Result<H2DispatchOutcome, ConnectorError> {
|
292 286 | if stage == UnacceptedStage::BeforeHyper || reused {
|
293 - | Ok(H2DispatchResult::Reacquire(Box::new(H2Reacquisition {
|
294 - | request,
|
295 - | error,
|
296 - | })))
|
287 + | Ok(H2DispatchOutcome::NotAccepted(Box::new(
|
288 + | H2UnacceptedRequest {
|
289 + | request,
|
290 + | terminal_error: error,
|
291 + | },
|
292 + | )))
|
297 293 | } else {
|
298 294 | Err(error)
|
299 295 | }
|
300 296 | }
|
301 297 |
|
302 298 | /// A fresh HTTP/2 generation closed before accepting its first request.
|
303 299 | #[derive(Debug)]
|
304 300 | struct H2ConnectionClosedBeforeDispatch;
|
305 301 |
|
306 302 | impl std::fmt::Display for H2ConnectionClosedBeforeDispatch {
|
307 303 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
308 304 | f.write_str("the fresh HTTP/2 connection closed before request dispatch")
|
309 305 | }
|
310 306 | }
|
311 307 |
|
312 308 | impl std::error::Error for H2ConnectionClosedBeforeDispatch {}
|
313 309 |
|
314 310 | /// Handle retained in a request extension after its body is wrapped once.
|
315 311 | #[derive(Clone)]
|
316 312 | struct H2RequestBodyHandle {
|
317 - | /// Re-armable send endpoint shared with the wrapped request body.
|
318 - | slot: Arc<Mutex<Option<H2LeaseEndpoint>>>,
|
313 + | /// Re-armable upload guard shared with the wrapped request body.
|
314 + | slot: Arc<Mutex<Option<H2UploadGuard>>>,
|
319 315 | }
|
320 316 |
|
321 317 | impl H2RequestBodyHandle {
|
322 - | /// Wraps an unwrapped body and arms the current send endpoint.
|
323 - | fn arm(request: &mut Request<SdkBody>, endpoint: H2LeaseEndpoint) -> Self {
|
318 + | /// Wraps an unwrapped body and arms the current upload guard.
|
319 + | fn arm(request: &mut Request<SdkBody>, upload: H2UploadGuard) -> Self {
|
324 320 | let is_end_stream = request.body().is_end_stream();
|
325 321 | let handle = request
|
326 322 | .extensions()
|
327 323 | .get::<Self>()
|
328 324 | .cloned()
|
329 325 | .unwrap_or_else(|| {
|
330 326 | let handle = Self {
|
331 327 | slot: Arc::new(Mutex::new(None)),
|
332 328 | };
|
333 329 | let body = std::mem::replace(request.body_mut(), SdkBody::taken());
|
334 330 | *request.body_mut() = SdkBody::from_body_1_x(H2RequestBody {
|
335 331 | inner: body,
|
336 332 | slot: handle.slot.clone(),
|
337 333 | });
|
338 334 | request.extensions_mut().insert(handle.clone());
|
339 335 | handle
|
340 336 | });
|
341 - | arm_send_endpoint(&handle.slot, endpoint);
|
337 + | arm_upload_guard(&handle.slot, upload);
|
342 338 | if is_end_stream {
|
343 - | finish_send_endpoint(&handle.slot);
|
339 + | finish_upload(&handle.slot);
|
344 340 | }
|
345 341 | handle
|
346 342 | }
|
347 343 |
|
348 - | /// Disarms an endpoint after Hyper returns the request unaccepted.
|
344 + | /// Disarms the upload guard after Hyper returns the request unaccepted.
|
349 345 | fn clear(&self) {
|
350 - | finish_send_endpoint(&self.slot);
|
346 + | finish_upload(&self.slot);
|
351 347 | }
|
352 348 | }
|
353 349 |
|
354 - | /// Request body whose endpoint can be re-armed after certified non-acceptance.
|
350 + | /// Request body whose upload guard can be re-armed after non-acceptance.
|
355 351 | struct H2RequestBody {
|
356 352 | /// Original SDK body wrapped exactly once.
|
357 353 | inner: SdkBody,
|
358 - | /// Endpoint replaced when Hyper returns an unaccepted request.
|
359 - | slot: Arc<Mutex<Option<H2LeaseEndpoint>>>,
|
354 + | /// Guard replaced when Hyper returns an unaccepted request.
|
355 + | slot: Arc<Mutex<Option<H2UploadGuard>>>,
|
360 356 | }
|
361 357 |
|
362 358 | impl Body for H2RequestBody {
|
363 359 | type Data = <SdkBody as Body>::Data;
|
364 360 | type Error = <SdkBody as Body>::Error;
|
365 361 |
|
366 362 | fn poll_frame(
|
367 363 | mut self: Pin<&mut Self>,
|
368 364 | cx: &mut Context<'_>,
|
369 365 | ) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
|
370 366 | let result = Pin::new(&mut self.inner).poll_frame(cx);
|
371 367 | if matches!(result, Poll::Ready(None) | Poll::Ready(Some(Err(_)))) {
|
372 - | finish_send_endpoint(&self.slot);
|
368 + | finish_upload(&self.slot);
|
373 369 | }
|
374 370 | result
|
375 371 | }
|
376 372 |
|
377 373 | fn is_end_stream(&self) -> bool {
|
378 374 | self.inner.is_end_stream()
|
379 375 | }
|
380 376 |
|
381 377 | fn size_hint(&self) -> hyper::body::SizeHint {
|
382 378 | self.inner.size_hint()
|
383 379 | }
|
384 380 | }
|
385 381 |
|
386 382 | impl Drop for H2RequestBody {
|
387 383 | fn drop(&mut self) {
|
388 - | finish_send_endpoint(&self.slot);
|
384 + | finish_upload(&self.slot);
|
389 385 | }
|
390 386 | }
|
391 387 |
|
392 - | /// Response body that owns the receive endpoint through stream completion.
|
388 + | /// Response body that owns the response guard through stream completion.
|
393 389 | struct H2ResponseBody {
|
394 390 | /// Hyper response stream.
|
395 391 | inner: hyper::body::Incoming,
|
396 - | /// Receive endpoint completed on terminal frame, error, or drop.
|
397 - | receive: Option<H2LeaseEndpoint>,
|
392 + | /// Response guard finished on terminal frame, error, or drop.
|
393 + | response: Option<H2ResponseGuard>,
|
398 394 | }
|
399 395 |
|
400 396 | impl H2ResponseBody {
|
401 - | /// Wraps a response and completes an endpoint already at end stream.
|
402 - | fn new(inner: hyper::body::Incoming, receive: H2LeaseEndpoint) -> Self {
|
397 + | /// Wraps a response and finishes a guard already at end stream.
|
398 + | fn new(inner: hyper::body::Incoming, response: H2ResponseGuard) -> Self {
|
403 399 | let is_end_stream = inner.is_end_stream();
|
404 400 | Self {
|
405 401 | inner,
|
406 - | receive: retain_receive_endpoint(is_end_stream, receive),
|
402 + | response: retain_response_guard(is_end_stream, response),
|
407 403 | }
|
408 404 | }
|
409 405 | }
|
410 406 |
|
411 - | /// Retains a receive endpoint only while response frames may remain.
|
412 - | fn retain_receive_endpoint(
|
407 + | /// Retains a response guard only while response frames may remain.
|
408 + | fn retain_response_guard(
|
413 409 | is_end_stream: bool,
|
414 - | receive: H2LeaseEndpoint,
|
415 - | ) -> Option<H2LeaseEndpoint> {
|
410 + | response: H2ResponseGuard,
|
411 + | ) -> Option<H2ResponseGuard> {
|
416 412 | if is_end_stream {
|
417 - | receive.complete();
|
413 + | response.finish();
|
418 414 | None
|
419 415 | } else {
|
420 - | Some(receive)
|
416 + | Some(response)
|
421 417 | }
|
422 418 | }
|
423 419 |
|
424 420 | impl Body for H2ResponseBody {
|
425 421 | type Data = <hyper::body::Incoming as Body>::Data;
|
426 422 | type Error = <hyper::body::Incoming as Body>::Error;
|
427 423 |
|
428 424 | fn poll_frame(
|
429 425 | mut self: Pin<&mut Self>,
|
430 426 | cx: &mut Context<'_>,
|
431 427 | ) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
|
432 428 | let result = Pin::new(&mut self.inner).poll_frame(cx);
|
433 429 | if matches!(result, Poll::Ready(None) | Poll::Ready(Some(Err(_)))) {
|
434 - | if let Some(endpoint) = self.receive.take() {
|
435 - | endpoint.complete();
|
430 + | if let Some(response) = self.response.take() {
|
431 + | response.finish();
|
436 432 | }
|
437 433 | }
|
438 434 | result
|
439 435 | }
|
440 436 |
|
441 437 | fn is_end_stream(&self) -> bool {
|
442 438 | self.inner.is_end_stream()
|
443 439 | }
|
444 440 |
|
445 441 | fn size_hint(&self) -> hyper::body::SizeHint {
|
446 442 | self.inner.size_hint()
|
447 443 | }
|
448 444 | }
|
449 445 |
|
450 - | fn arm_send_endpoint(slot: &Arc<Mutex<Option<H2LeaseEndpoint>>>, endpoint: H2LeaseEndpoint) {
|
451 - | let previous = slot.lock().replace(endpoint);
|
446 + | fn arm_upload_guard(slot: &Arc<Mutex<Option<H2UploadGuard>>>, upload: H2UploadGuard) {
|
447 + | let previous = slot.lock().replace(upload);
|
452 448 | drop(previous);
|
453 449 | }
|
454 450 |
|
455 - | /// Terminates the send endpoint after upload completion, error, or rejection.
|
456 - | fn finish_send_endpoint(slot: &Arc<Mutex<Option<H2LeaseEndpoint>>>) {
|
457 - | let endpoint = slot.lock().take();
|
458 - | if let Some(endpoint) = endpoint {
|
459 - | endpoint.complete();
|
451 + | /// Finishes the upload guard after upload completion, error, or rejection.
|
452 + | fn finish_upload(slot: &Arc<Mutex<Option<H2UploadGuard>>>) {
|
453 + | let upload = slot.lock().take();
|
454 + | if let Some(upload) = upload {
|
455 + | upload.finish();
|
460 456 | }
|
461 457 | }
|
462 458 |
|
463 459 | #[cfg(all(test, not(smithy_http_client_loom), feature = "rt-tokio"))]
|
464 460 | mod tests {
|
465 461 | use super::*;
|
466 462 | use crate::client::pool::cell::OriginCell;
|
467 463 | use crate::client::pool::origin::OriginKey;
|
468 464 | use crate::client::pool::partition::{EligibilityGroup, PartitionId};
|
469 465 | use crate::sync::Arc;
|
470 466 | use http_1x::uri::Scheme;
|
471 467 | use http_body_util::BodyExt;
|
472 468 |
|
473 469 | /// Request body that fails on its first frame.
|
474 470 | struct FailingBody;
|
475 471 |
|
476 472 | impl Body for FailingBody {
|
477 473 | type Data = hyper::body::Bytes;
|
478 474 | type Error = std::io::Error;
|
479 475 |
|
480 476 | fn poll_frame(
|
481 477 | self: Pin<&mut Self>,
|
482 478 | _cx: &mut Context<'_>,
|
483 479 | ) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
|
484 480 | Poll::Ready(Some(Err(std::io::Error::other("request body failed"))))
|
485 481 | }
|
486 482 | }
|
487 483 |
|
488 484 | fn cell() -> Arc<OriginCell> {
|
489 485 | Arc::new(OriginCell::new(
|
490 486 | PartitionId::from_index(1),
|
491 487 | OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap(),
|
492 488 | EligibilityGroup::Pool,
|
493 489 | None,
|
494 490 | None,
|
495 491 | ))
|
496 492 | }
|
497 493 |
|
498 494 | #[tokio::test]
|
499 495 | async fn returned_request_rearms_one_body_wrapper() {
|
500 496 | let cell = cell();
|
501 497 | let mut request = Request::post("https://example.com/upload")
|
502 498 | .body(SdkBody::from("payload"))
|
503 499 | .unwrap();
|
504 500 |
|
505 - | let (first_endpoint, first_probe) = H2LeaseEndpoint::send_for_test(&cell);
|
506 - | let first = H2RequestBodyHandle::arm(&mut request, first_endpoint);
|
501 + | let (first_upload, first_probe) = H2UploadGuard::for_test(&cell);
|
502 + | let first = H2RequestBodyHandle::arm(&mut request, first_upload);
|
507 503 | let first_slot = first.slot.clone();
|
508 - | assert!(!first_probe.send_complete());
|
504 + | assert!(!first_probe.upload_finished());
|
509 505 |
|
510 - | let (second_endpoint, second_probe) = H2LeaseEndpoint::send_for_test(&cell);
|
511 - | let second = H2RequestBodyHandle::arm(&mut request, second_endpoint);
|
506 + | let (second_upload, second_probe) = H2UploadGuard::for_test(&cell);
|
507 + | let second = H2RequestBodyHandle::arm(&mut request, second_upload);
|
512 508 | assert!(
|
513 509 | Arc::ptr_eq(&first_slot, &second.slot),
|
514 510 | "rearming replaced the request-body wrapper"
|
515 511 | );
|
516 512 | assert!(
|
517 - | first_probe.send_complete(),
|
518 - | "rearming did not cancel the prior prospective endpoint"
|
513 + | first_probe.upload_finished(),
|
514 + | "rearming did not cancel the prior prospective upload guard"
|
519 515 | );
|
520 - | assert!(!second_probe.send_complete());
|
516 + | assert!(!second_probe.upload_finished());
|
521 517 |
|
522 518 | second.clear();
|
523 519 | assert!(
|
524 - | second_probe.send_complete(),
|
525 - | "returned request retained its rejected send endpoint"
|
520 + | second_probe.upload_finished(),
|
521 + | "returned request retained its rejected upload guard"
|
526 522 | );
|
527 523 | let body = request
|
528 524 | .into_body()
|
529 525 | .collect()
|
530 526 | .await
|
531 527 | .expect("wrapped request body failed")
|
532 528 | .to_bytes();
|
533 529 | assert_eq!(hyper::body::Bytes::from_static(b"payload"), body);
|
534 530 | }
|
535 531 |
|
536 532 | #[test]
|
537 - | fn empty_request_body_completes_its_send_endpoint_when_armed() {
|
533 + | fn empty_request_body_finishes_its_upload_guard_when_armed() {
|
538 534 | let cell = cell();
|
539 535 | let mut request = Request::new(SdkBody::empty());
|
540 - | let (endpoint, probe) = H2LeaseEndpoint::send_for_test(&cell);
|
536 + | let (upload, probe) = H2UploadGuard::for_test(&cell);
|
541 537 |
|
542 - | H2RequestBodyHandle::arm(&mut request, endpoint);
|
538 + | H2RequestBodyHandle::arm(&mut request, upload);
|
543 539 |
|
544 - | assert!(probe.send_complete());
|
540 + | assert!(probe.upload_finished());
|
545 541 | }
|
542 + |
|
546 543 | #[test]
|
547 - | fn dropping_request_body_completes_its_send_endpoint() {
|
544 + | fn dropping_request_body_finishes_its_upload_guard() {
|
548 545 | let cell = cell();
|
549 546 | let mut request = Request::new(SdkBody::from("payload"));
|
550 - | let (endpoint, probe) = H2LeaseEndpoint::send_for_test(&cell);
|
547 + | let (upload, probe) = H2UploadGuard::for_test(&cell);
|
551 548 |
|
552 - | H2RequestBodyHandle::arm(&mut request, endpoint);
|
549 + | H2RequestBodyHandle::arm(&mut request, upload);
|
553 550 | let body = std::mem::replace(request.body_mut(), SdkBody::empty());
|
554 551 | drop(body);
|
555 552 |
|
556 - | assert!(probe.send_complete());
|
553 + | assert!(probe.upload_finished());
|
557 554 | }
|
558 555 |
|
559 556 | #[tokio::test]
|
560 - | async fn request_body_error_completes_its_send_endpoint() {
|
557 + | async fn request_body_error_finishes_its_upload_guard() {
|
561 558 | let cell = cell();
|
562 559 | let mut request = Request::new(SdkBody::from_body_1_x(FailingBody));
|
563 - | let (endpoint, probe) = H2LeaseEndpoint::send_for_test(&cell);
|
560 + | let (upload, probe) = H2UploadGuard::for_test(&cell);
|
564 561 |
|
565 - | H2RequestBodyHandle::arm(&mut request, endpoint);
|
562 + | H2RequestBodyHandle::arm(&mut request, upload);
|
566 563 | let frame = request
|
567 564 | .body_mut()
|
568 565 | .frame()
|
569 566 | .await
|
570 567 | .expect("failing body omitted its error frame");
|
571 568 |
|
572 569 | assert!(frame.is_err());
|
573 - | assert!(probe.send_complete());
|
570 + | assert!(probe.upload_finished());
|
574 571 | }
|
575 572 |
|
576 573 | #[test]
|
577 - | fn bodyless_response_completes_its_receive_endpoint_without_drop() {
|
574 + | fn bodyless_response_finishes_its_response_guard_without_drop() {
|
578 575 | let cell = cell();
|
579 - | let (endpoint, probe) = H2LeaseEndpoint::receive_for_test(&cell);
|
576 + | let (response, probe) = H2ResponseGuard::for_test(&cell);
|
580 577 |
|
581 - | let retained = retain_receive_endpoint(true, endpoint);
|
578 + | let retained = retain_response_guard(true, response);
|
582 579 |
|
583 580 | assert!(retained.is_none());
|
584 - | assert!(probe.receive_complete());
|
581 + | assert!(probe.response_finished());
|
585 582 | }
|
586 583 |
|
587 584 | #[test]
|
588 585 | fn h2_preparation_restores_uri_and_removes_only_synthesized_host() {
|
589 586 | let absolute: http_1x::Uri = "https://example.com/resource".parse().unwrap();
|
590 587 | let mut request = Request::get("/resource").body(SdkBody::empty()).unwrap();
|
591 588 | request
|
592 589 | .headers_mut()
|
593 590 | .insert(http_1x::header::HOST, "example.com".parse().unwrap());
|
594 591 | request.extensions_mut().insert(H1HostHeaderInserted);
|
595 592 |
|
596 593 | prepare_h2_request(&mut request, &absolute);
|
597 594 |
|
598 595 | assert_eq!(&absolute, request.uri());
|
599 596 | assert!(!request.headers().contains_key(http_1x::header::HOST));
|
600 597 | assert!(request.extensions().get::<H1HostHeaderInserted>().is_none());
|
601 598 |
|
602 599 | let mut user_host = Request::get("/resource").body(SdkBody::empty()).unwrap();
|
603 600 | user_host
|
604 601 | .headers_mut()
|
605 602 | .insert(http_1x::header::HOST, "signed.example".parse().unwrap());
|
606 603 | prepare_h2_request(&mut user_host, &absolute);
|
607 604 | assert_eq!("signed.example", user_host.headers()[http_1x::header::HOST]);
|
608 605 | }
|
609 606 |
|
610 607 | #[test]
|
611 608 | fn retry_authority_distinguishes_pool_checks_from_hyper_returns() {
|
612 - | let checked_before_hyper = finish_unaccepted_request(
|
609 + | let checked_before_hyper = resolve_unaccepted_request(
|
613 610 | Request::new(SdkBody::empty()),
|
614 611 | false,
|
615 612 | UnacceptedStage::BeforeHyper,
|
616 613 | ConnectorError::user("pre-Hyper failure".into()),
|
617 614 | );
|
618 615 | assert!(matches!(
|
619 616 | checked_before_hyper,
|
620 - | Ok(H2DispatchResult::Reacquire(_))
|
617 + | Ok(H2DispatchOutcome::NotAccepted(_))
|
621 618 | ));
|
622 619 |
|
623 - | let fresh_hyper_return = finish_unaccepted_request(
|
620 + | let fresh_hyper_return = resolve_unaccepted_request(
|
624 621 | Request::new(SdkBody::empty()),
|
625 622 | false,
|
626 623 | UnacceptedStage::ReturnedByHyper,
|
627 624 | ConnectorError::user("fresh Hyper failure".into()),
|
628 625 | );
|
629 626 | assert!(fresh_hyper_return.is_err());
|
630 627 |
|
631 - | let reused_hyper_return = finish_unaccepted_request(
|
628 + | let reused_hyper_return = resolve_unaccepted_request(
|
632 629 | Request::new(SdkBody::empty()),
|
633 630 | true,
|
634 631 | UnacceptedStage::ReturnedByHyper,
|
635 632 | ConnectorError::user("reused failure".into()),
|
636 633 | );
|
637 634 | assert!(matches!(
|
638 635 | reused_hyper_return,
|
639 - | Ok(H2DispatchResult::Reacquire(_))
|
636 + | Ok(H2DispatchOutcome::NotAccepted(_))
|
640 637 | ));
|
641 638 | }
|
642 639 | }
|