Skip to content

Commit dc41bbe

Browse files
committed
fix: Missed wakeup in poll_trailers
poll_trailers returned Pending without storing a waker when a non-trailer event was at the front of pending_recv.
1 parent 27b730e commit dc41bbe

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

src/proto/streams/recv.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,7 @@ impl Recv {
12301230
Some(event) => {
12311231
// Frame is not trailers.. not ready to poll trailers yet.
12321232
stream.pending_recv.push_front(&mut self.buffer, event);
1233-
1233+
stream.recv_task = Some(cx.waker().clone());
12341234
Poll::Pending
12351235
}
12361236
None => self.schedule_recv(cx, stream),

tests/h2-tests/tests/trailers.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use futures::StreamExt;
22
use h2_support::prelude::*;
3+
use std::task::Poll;
34

45
#[tokio::test]
56
async fn recv_trailers_only() {
@@ -110,6 +111,90 @@ fn recv_trailers_without_eos() {
110111
// This should be a protocol error?
111112
}
112113

114+
#[tokio::test]
115+
async fn poll_trailers_before_data_is_consumed() {
116+
h2_support::trace_init!();
117+
let (io, mut srv) = mock::new();
118+
119+
let srv = async move {
120+
let settings = srv.assert_client_handshake().await;
121+
assert_default_settings!(settings);
122+
123+
// 2. Receive the request.
124+
srv.recv_frame(
125+
frames::headers(1)
126+
.request("GET", "https://example.com/")
127+
.eos(),
128+
)
129+
.await;
130+
131+
// 3. Send response HEADERS followed by DATA and trailers.
132+
srv.send_frame(frames::headers(1).response(200)).await;
133+
srv.send_frame(frames::data(1, "hello")).await;
134+
srv.send_frame(frames::headers(1).field("trailer-key", "trailer-val").eos())
135+
.await;
136+
137+
// 4. Ensure all preceding frames have been processed by the client.
138+
srv.ping_pong([1; 8]).await;
139+
};
140+
141+
let client = async move {
142+
let (mut client, conn) = client::handshake(io).await.expect("handshake");
143+
let conn = tokio::spawn(async move {
144+
conn.await.expect("client");
145+
});
146+
147+
// 1. Send the request and wait for response HEADERS.
148+
let resp = client.get("https://example.com/").await.expect("response");
149+
assert_eq!(resp.status(), StatusCode::OK);
150+
151+
let mut body = resp.into_body();
152+
let mut first_poll = true;
153+
154+
let trailers = tokio::time::timeout(
155+
Duration::from_secs(1),
156+
poll_fn(|cx| {
157+
if first_poll {
158+
// 5. Poll trailers while DATA is at the front of pending_recv.
159+
// This returns Pending and registers this future's waker.
160+
first_poll = false;
161+
assert!(
162+
matches!(body.poll_trailers(cx), Poll::Pending),
163+
"poll_trailers should be Pending when DATA is buffered"
164+
);
165+
166+
// 6. Consume the DATA frame. The next poll reaches the
167+
// queued trailers and wakes the waker registered in 5.
168+
match body.poll_data(cx) {
169+
Poll::Ready(Some(Ok(data))) => assert_eq!(data, "hello"),
170+
other => panic!("expected DATA, got {:?}", other),
171+
}
172+
assert!(matches!(body.poll_data(cx), Poll::Ready(None)));
173+
174+
Poll::Pending
175+
} else {
176+
// 7. This future must only be polled again after
177+
// poll_data's notify_recv wakes it.
178+
body.poll_trailers(cx)
179+
}
180+
})
181+
.wakened(),
182+
)
183+
.await
184+
.expect("poll_trailers was not woken")
185+
.expect("trailers result")
186+
.expect("should have trailers");
187+
188+
assert_eq!(trailers["trailer-key"], "trailer-val");
189+
190+
drop(body);
191+
drop(client);
192+
conn.await.unwrap();
193+
};
194+
195+
join(srv, client).await;
196+
}
197+
113198
#[tokio::test]
114199
async fn send_trailers_rejects_connection_specific_headers() {
115200
// RFC 9113 §8.2.2: endpoints MUST NOT *generate* an HTTP/2 message containing

0 commit comments

Comments
 (0)