Skip to content

Commit 47f3aef

Browse files
apollo_infra: add client connection eviction test
1 parent 732e555 commit 47f3aef

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

crates/apollo_infra/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ pub(crate) mod test_utils;
33
mod concurrent_servers_test;
44
mod local_component_client_server_test;
55
mod local_request_prioritization_test;
6+
mod remote_client_connection_eviction_test;
67
mod remote_component_client_server_test;
78
mod server_metrics_test;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::convert::Infallible;
2+
use std::sync::atomic::{AtomicUsize, Ordering};
3+
use std::sync::Arc;
4+
use std::time::Duration;
5+
6+
use apollo_proc_macros::unique_u16;
7+
use bytes::Bytes;
8+
use http::header::CONTENT_TYPE;
9+
use http::StatusCode;
10+
use http_body_util::Full;
11+
use hyper::body::Incoming;
12+
use hyper::service::service_fn;
13+
use hyper::{Request, Response};
14+
use hyper_util::rt::{TokioExecutor, TokioIo};
15+
use hyper_util::server::conn::auto::Builder as Http2ServerBuilder;
16+
use tokio::net::TcpListener;
17+
use tokio::task;
18+
use tokio::time::sleep;
19+
20+
use crate::component_client::RemoteClientConfig;
21+
use crate::component_definitions::APPLICATION_OCTET_STREAM;
22+
use crate::serde_utils::SerdeWrapper;
23+
use crate::tests::test_utils::{
24+
available_ports_factory,
25+
ComponentAClient,
26+
ComponentAClientTrait,
27+
ComponentAResponse,
28+
TEST_REMOTE_CLIENT_METRICS,
29+
VALID_VALUE_A,
30+
};
31+
32+
/// Verifies that Hyper evicts an idle connection from its pool after the keepalive timeout
33+
/// and opens a fresh TCP connection for the next request.
34+
///
35+
/// Since no pool timer is configured, eviction is triggered by the next pool checkout (the
36+
/// second request), not a background task. A new connection is detected by counting
37+
/// server-side accepts.
38+
#[tokio::test]
39+
async fn idle_connection_is_evicted_after_pool_timeout() {
40+
const KEEPALIVE_TIMEOUT_MS: u64 = 100;
41+
const MARGIN_MS: u64 = 300;
42+
43+
let socket = available_ports_factory(unique_u16!()).get_next_local_host_socket();
44+
let accept_count = Arc::new(AtomicUsize::new(0));
45+
46+
// A server that accepts connections and keeps track of the number of connections it has
47+
// accepted.
48+
{
49+
let accept_count = accept_count.clone();
50+
task::spawn(async move {
51+
let listener = TcpListener::bind(socket).await.unwrap();
52+
loop {
53+
let Ok((stream, _)) = listener.accept().await else { continue };
54+
accept_count.fetch_add(1, Ordering::SeqCst);
55+
let io = TokioIo::new(stream);
56+
let service = service_fn(|_req: Request<Incoming>| async {
57+
let body = ComponentAResponse::AGetValue(VALID_VALUE_A);
58+
Ok::<_, Infallible>(
59+
Response::builder()
60+
.status(StatusCode::OK)
61+
.header(CONTENT_TYPE, APPLICATION_OCTET_STREAM)
62+
.body(Full::new(Bytes::from(
63+
SerdeWrapper::new(body).wrapper_serialize().unwrap(),
64+
)))
65+
.unwrap(),
66+
)
67+
});
68+
tokio::spawn(async move {
69+
let _ = Http2ServerBuilder::new(TokioExecutor::new())
70+
.http2()
71+
.serve_connection(io, service)
72+
.await;
73+
});
74+
}
75+
});
76+
}
77+
task::yield_now().await;
78+
79+
let client = ComponentAClient::new(
80+
RemoteClientConfig { keepalive_timeout_ms: KEEPALIVE_TIMEOUT_MS, ..Default::default() },
81+
&socket.ip().to_string(),
82+
socket.port(),
83+
&TEST_REMOTE_CLIENT_METRICS,
84+
);
85+
86+
// Establish connection C1.
87+
client.a_get_value().await.expect("first request should succeed");
88+
assert_eq!(accept_count.load(Ordering::SeqCst), 1, "C1 should have been accepted");
89+
90+
// Let the idle timeout expire.
91+
sleep(Duration::from_millis(KEEPALIVE_TIMEOUT_MS + MARGIN_MS)).await;
92+
93+
// The next checkout detects C1 is expired, drops it, and opens a new connection C2.
94+
client.a_get_value().await.expect("second request should succeed");
95+
assert_eq!(
96+
accept_count.load(Ordering::SeqCst),
97+
2,
98+
"idle timeout should have evicted C1 and caused a new connection C2"
99+
);
100+
}

0 commit comments

Comments
 (0)