use std::convert::Infallible;
use std::time::Duration;
use bytes::Bytes;
use sui_http::Config;
async fn wait_for_no_connections(handle: &sui_http::ServerHandle) {
tokio::time::timeout(Duration::from_secs(10), async {
while handle.number_of_connections() > 0 {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("connection was never reclaimed");
}
#[tokio::test]
async fn age_with_grace_force_closes_wedged_connection() {
let app = axum::Router::new().route(
"/",
axum::routing::get(|| async { std::future::pending::<String>().await }),
);
let config = Config::default()
.max_connection_age(Duration::from_millis(300))
.max_connection_age_grace(Duration::from_millis(300));
let handle = sui_http::Builder::new()
.config(config)
.serve(("localhost", 0), app)
.unwrap();
let client = reqwest::Client::builder()
.http2_prior_knowledge()
.build()
.unwrap();
let url = format!("http://{}", handle.local_addr());
let result = tokio::time::timeout(Duration::from_secs(10), client.get(url).send())
.await
.expect("request hung: connection was never force-closed");
assert!(result.is_err());
wait_for_no_connections(&handle).await;
}
#[tokio::test]
async fn age_with_grace_reclaims_send_stalled_connection() {
let app = axum::Router::new().route(
"/stream",
axum::routing::get(|| async {
let stream = futures::stream::repeat_with(|| {
Ok::<_, Infallible>(Bytes::from_static(&[0u8; 16 * 1024]))
});
axum::body::Body::from_stream(stream)
}),
);
let config = Config::default()
.max_connection_age(Duration::from_millis(300))
.max_connection_age_grace(Duration::from_millis(300));
let handle = sui_http::Builder::new()
.config(config)
.serve(("localhost", 0), app)
.unwrap();
let addr = *handle.local_addr();
let tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
let (mut send_request, connection) = h2::client::handshake(tcp).await.unwrap();
tokio::spawn(connection);
let request = http::Request::builder()
.uri(format!("http://{addr}/stream"))
.body(())
.unwrap();
send_request = send_request.ready().await.unwrap();
let (response, _) = send_request.send_request(request, true).unwrap();
let response = response.await.unwrap();
assert!(response.status().is_success());
let mut body = response.into_body();
let first = body.data().await.expect("first chunk").unwrap();
assert!(!first.is_empty());
wait_for_no_connections(&handle).await;
drop(body);
drop(send_request);
}
#[tokio::test]
async fn close_with_grace_force_closes_wedged_connection() {
let app = axum::Router::new().route(
"/",
axum::routing::get(|| async { std::future::pending::<String>().await }),
);
let config = Config::default().max_connection_age_grace(Duration::from_millis(300));
let handle = sui_http::Builder::new()
.config(config)
.serve(("localhost", 0), app)
.unwrap();
let client = reqwest::Client::builder()
.http2_prior_knowledge()
.build()
.unwrap();
let url = format!("http://{}", handle.local_addr());
let request = tokio::spawn(client.get(url).send());
tokio::time::timeout(Duration::from_secs(10), async {
while handle.number_of_connections() == 0 {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("connection was never established");
for connection in handle.connections().values() {
connection.close();
}
let result = tokio::time::timeout(Duration::from_secs(10), request)
.await
.expect("request hung: connection was never force-closed")
.unwrap();
assert!(result.is_err());
wait_for_no_connections(&handle).await;
}
#[tokio::test]
async fn age_without_grace_waits_for_in_flight_requests() {
let (release_tx, release_rx) = tokio::sync::watch::channel(false);
let app = axum::Router::new().route(
"/",
axum::routing::get(move || {
let mut release_rx = release_rx.clone();
async move {
let _ = release_rx.wait_for(|released| *released).await;
"ok"
}
}),
);
let config = Config::default().max_connection_age(Duration::from_millis(200));
let handle = sui_http::Builder::new()
.config(config)
.serve(("localhost", 0), app)
.unwrap();
let client = reqwest::Client::builder()
.http2_prior_knowledge()
.build()
.unwrap();
let url = format!("http://{}", handle.local_addr());
let request = tokio::spawn(client.get(url).send());
tokio::time::sleep(Duration::from_secs(1)).await;
assert_eq!(handle.number_of_connections(), 1);
assert!(!request.is_finished());
release_tx.send(true).unwrap();
let response = tokio::time::timeout(Duration::from_secs(10), request)
.await
.expect("request did not complete after release")
.unwrap()
.unwrap();
assert!(response.status().is_success());
wait_for_no_connections(&handle).await;
}