fix: bound HTTP intake and response lifetimes

This commit is contained in:
2026-09-30 09:23:12 +09:00
parent f5034c0e4b
commit 780150d7ae
6 changed files with 364 additions and 106 deletions
+69 -1
View File
@@ -4,7 +4,7 @@ use std::net::{SocketAddr, TcpStream};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use std::time::{Duration, Instant};
use tempfile::tempdir;
use wip_fs_server::{AccessMode, ServerConfig, TransportSecurity, WipFsServer};
@@ -202,6 +202,74 @@ fn real_listener_serves_four_core_interactions_batch_and_write() {
.expect("listener exits cleanly");
}
#[test]
fn stalled_request_body_times_out_and_releases_the_only_worker() {
let temporary = tempdir().expect("temporary root");
let mut config = ServerConfig::for_root(temporary.path());
config.transport_security = TransportSecurity::DevelopmentHttp;
config.limits.max_concurrent_requests = 1;
config.limits.request_timeout = Duration::from_millis(200);
let limits = config.limits.wire;
let bound = Arc::new(
WipFsServer::new(config)
.expect("build server")
.bind()
.expect("bind"),
);
let address = bound.local_addr();
let shutdown = Arc::new(AtomicBool::new(false));
let worker = {
let bound = Arc::clone(&bound);
let shutdown = Arc::clone(&shutdown);
thread::spawn(move || bound.serve_until(shutdown))
};
let mut stalled = TcpStream::connect(address).expect("connect stalled client");
stalled
.set_read_timeout(Some(Duration::from_secs(1)))
.expect("set stalled read timeout");
write!(
stalled,
"POST /wip/v1/fetch HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nAccept: application/json\r\nContent-Length: 100\r\n\r\n{{"
)
.expect("write partial request");
stalled.flush().expect("flush partial request");
thread::sleep(Duration::from_millis(25));
let endpoint = Endpoint::parse(&format!("https://localhost:{}/wip", address.port()))
.expect("client endpoint");
let fetch = FetchRequest { path: "/".into() };
let started = Instant::now();
let response = exchange(
address,
encode_fetch_request(&endpoint, &fetch, limits).expect("encode fetch"),
);
let _root = success(decode_fetch_response(&fetch, &response, limits).expect("decode fetch"));
assert!(
started.elapsed() < Duration::from_secs(1),
"the valid request remained starved after the intake deadline"
);
let mut byte = [0_u8; 1];
match stalled.read(&mut byte) {
Ok(0) => {}
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::BrokenPipe
) => {}
outcome => panic!("stalled connection was not closed after its deadline: {outcome:?}"),
}
shutdown.store(true, Ordering::Release);
worker
.join()
.expect("join listener")
.expect("listener exits cleanly");
}
#[test]
fn batch_keeps_known_malformed_arguments_item_local() {
let temporary = tempdir().expect("temporary root");