Skip to content

Commit 62f9c25

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 62f9c25

2 files changed

Lines changed: 85 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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use futures::StreamExt;
22
use h2_support::prelude::*;
3+
use std::sync::atomic::{AtomicBool, Ordering};
4+
use std::sync::Arc;
5+
use std::task::{Context, Poll, Wake, Waker};
36

47
#[tokio::test]
58
async fn recv_trailers_only() {
@@ -110,6 +113,87 @@ fn recv_trailers_without_eos() {
110113
// This should be a protocol error?
111114
}
112115

116+
#[tokio::test]
117+
async fn poll_trailers_before_data_is_consumed() {
118+
h2_support::trace_init!();
119+
let (io, mut srv) = mock::new();
120+
121+
let srv = async move {
122+
let settings = srv.assert_client_handshake().await;
123+
assert_default_settings!(settings);
124+
125+
// 2. Receive the request.
126+
srv.recv_frame(
127+
frames::headers(1)
128+
.request("GET", "https://example.com/")
129+
.eos(),
130+
)
131+
.await;
132+
133+
// 3. Send response HEADERS followed by DATA and trailers.
134+
srv.send_frame(frames::headers(1).response(200)).await;
135+
srv.send_frame(frames::data(1, "hello")).await;
136+
srv.send_frame(frames::headers(1).field("trailer-key", "trailer-val").eos())
137+
.await;
138+
139+
// 4. Ensure all preceding frames have been processed by the client.
140+
srv.ping_pong([1; 8]).await;
141+
};
142+
143+
let client = async move {
144+
let (mut client, conn) = client::handshake(io).await.expect("handshake");
145+
146+
let req = async move {
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+
153+
struct FlagWake(AtomicBool);
154+
impl Wake for FlagWake {
155+
fn wake(self: Arc<Self>) {
156+
self.0.store(true, Ordering::SeqCst);
157+
}
158+
}
159+
160+
let flag = Arc::new(FlagWake(AtomicBool::new(false)));
161+
let waker = Waker::from(flag.clone());
162+
let mut cx = Context::from_waker(&waker);
163+
164+
// 5. Poll trailers while DATA is at the front of pending_recv.
165+
// This returns Pending and must register the trailers waker.
166+
assert!(
167+
matches!(body.poll_trailers(&mut cx), Poll::Pending),
168+
"poll_trailers should be Pending when DATA is buffered"
169+
);
170+
171+
// 6. Consume the DATA frame.
172+
let data = body.data().await.unwrap().unwrap();
173+
assert_eq!(data, "hello");
174+
175+
// 7. poll_data reaches the queued trailers and calls notify_recv().
176+
// The waker registered in 5 must be woken.
177+
assert!(body.data().await.is_none());
178+
assert!(
179+
flag.0.load(Ordering::SeqCst),
180+
"poll_trailers waker must be notified when DATA is consumed"
181+
);
182+
183+
// 8. Poll trailers again and consume them.
184+
let trailers = body.trailers().await.expect("trailers result");
185+
let trailers = trailers.expect("should have trailers");
186+
assert_eq!(trailers["trailer-key"], "trailer-val");
187+
};
188+
189+
let mut conn = Box::pin(async move { conn.await.expect("client") });
190+
conn.drive(req).await;
191+
conn.await;
192+
};
193+
194+
join(srv, client).await;
195+
}
196+
113197
#[tokio::test]
114198
async fn send_trailers_rejects_connection_specific_headers() {
115199
// RFC 9113 §8.2.2: endpoints MUST NOT *generate* an HTTP/2 message containing

0 commit comments

Comments
 (0)