Skip to content

Commit 3f7a905

Browse files
Robin Seitzseanmonstar
authored andcommitted
fix(http1): http1 server graceful shutdown fix
fix issue in the graceful shutdown logic which causes the connection future to hang when graceful shutdown is called prior to any requests being made. This fix checks to see if the connection is still in its initial state when disable_keep_alive is called, and starts the shutdown process if it is. This addresses issue #2730
1 parent d92d391 commit 3f7a905

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

src/proto/h1/conn.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ where
175175
}
176176
}
177177

178+
#[cfg(feature = "server")]
179+
pub(crate) fn has_initial_read_write_state(&self) -> bool {
180+
matches!(self.state.reading, Reading::Init)
181+
&& matches!(self.state.writing, Writing::Init)
182+
&& self.io.read_buf().is_empty()
183+
}
184+
178185
fn should_error_on_eof(&self) -> bool {
179186
// If we're idle, it's probably just the connection closing gracefully.
180187
T::should_error_on_parse_eof() && !self.state.is_idle()

src/proto/h1/dispatch.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ where
8282
#[cfg(feature = "server")]
8383
pub(crate) fn disable_keep_alive(&mut self) {
8484
self.conn.disable_keep_alive();
85-
if self.conn.is_write_closed() {
85+
86+
// If keep alive has been disabled and no read or write has been seen on
87+
// the connection yet, we must be in a state where the server is being asked to
88+
// shut down before any data has been seen on the connection
89+
if self.conn.is_write_closed() || self.conn.has_initial_read_write_state() {
8690
self.close();
8791
}
8892
}

tests/server.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use hyper::body::{Body, Incoming as IncomingBody};
3131
use hyper::server::conn::{http1, http2};
3232
use hyper::service::{service_fn, Service};
3333
use hyper::{Method, Request, Response, StatusCode, Uri, Version};
34+
use tokio::pin;
3435

3536
mod support;
3637

@@ -1144,6 +1145,11 @@ async fn disable_keep_alive_mid_request() {
11441145
req.write_all(b"Host: localhost\r\n\r\n").unwrap();
11451146
let mut buf = vec![];
11461147
req.read_to_end(&mut buf).unwrap();
1148+
assert!(
1149+
buf.starts_with(b"HTTP/1.1 200 OK\r\n"),
1150+
"should receive OK response, but buf: {:?}",
1151+
buf,
1152+
);
11471153
});
11481154

11491155
let (socket, _) = listener.accept().await.unwrap();
@@ -2152,6 +2158,31 @@ async fn max_buf_size() {
21522158
.expect_err("should TooLarge error");
21532159
}
21542160

2161+
#[cfg(feature = "http1")]
2162+
#[tokio::test]
2163+
async fn graceful_shutdown_before_first_request_no_block() {
2164+
let (listener, addr) = setup_tcp_listener();
2165+
2166+
tokio::spawn(async move {
2167+
let socket = listener.accept().await.unwrap().0;
2168+
2169+
let future = http1::Builder::new().serve_connection(socket, HelloWorld);
2170+
pin!(future);
2171+
future.as_mut().graceful_shutdown();
2172+
2173+
future.await.unwrap();
2174+
});
2175+
2176+
let mut stream = TkTcpStream::connect(addr).await.unwrap();
2177+
2178+
let mut buf = vec![];
2179+
2180+
tokio::time::timeout(Duration::from_secs(5), stream.read_to_end(&mut buf))
2181+
.await
2182+
.expect("timed out waiting for graceful shutdown")
2183+
.expect("error receiving response");
2184+
}
2185+
21552186
#[test]
21562187
fn streaming_body() {
21572188
use futures_util::StreamExt;

0 commit comments

Comments
 (0)