mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
fix(collab-net): hold a health check's header read to the handshake deadline
The request line was read within the hello timeout, but the header loop after it only timed out per read, so a peer dripping header bytes kept the worker thread and its connection slot for as long as it liked. Each header read now gets what is left of the one deadline.
This commit is contained in:
1 parent
02c38a6d95
commit
4e7d8319f1
1 file changed
+34
@@ -51,6 +51,12 @@ fn health_check(stream: &mut TcpStream, until: Instant) -> bool {
|
||||
let mut req = Vec::new();
|
||||
let mut chunk = [0u8; 1024];
|
||||
while !req.windows(4).any(|w| w == b"\r\n\r\n") && req.len() < 16 << 10 {
|
||||
// The same deadline as the request line: a peer dripping header bytes would otherwise
|
||||
// hold this thread and its connection slot for as long as it liked.
|
||||
let left = until.saturating_duration_since(Instant::now());
|
||||
if left.is_zero() || stream.set_read_timeout(Some(left)).is_err() {
|
||||
break;
|
||||
}
|
||||
match stream.read(&mut chunk) {
|
||||
Ok(0) | Err(_) => break,
|
||||
Ok(n) => req.extend_from_slice(chunk.get(..n).unwrap_or_default()),
|
||||
@@ -407,3 +413,31 @@ fn linger(ws: &mut WebSocket<Duplex>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod health_check_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_health_check_dripping_headers_ends_at_the_deadline() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let client = std::thread::spawn(move || {
|
||||
let mut c = TcpStream::connect(addr).unwrap();
|
||||
c.write_all(b"GET /healthz HTTP/1.1\r\n").unwrap();
|
||||
// One header byte every 20 ms, far past the deadline, until the server hangs up.
|
||||
for _ in 0..200 {
|
||||
if c.write_all(b"X").is_err() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
});
|
||||
let (mut stream, _) = listener.accept().unwrap();
|
||||
let start = Instant::now();
|
||||
assert!(health_check(&mut stream, start + Duration::from_millis(300)));
|
||||
assert!(start.elapsed() < Duration::from_secs(2), "{:?}", start.elapsed());
|
||||
drop(stream);
|
||||
client.join().unwrap();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user