From 57e08729438b8900895a73c00f158d6a97296a46 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 22 Oct 2026 20:56:58 +0200 Subject: [PATCH] Bound the library/plugins close drain with a deadline Fixes #1120 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/collab-net/src/manage.rs | 37 +++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/collab-net/src/manage.rs b/crates/collab-net/src/manage.rs index 6e995bdb..dc3ec315 100644 --- a/crates/collab-net/src/manage.rs +++ b/crates/collab-net/src/manage.rs @@ -206,10 +206,15 @@ fn connect(server: &Endpoint, limits: &NetLimits, timeout: Duration) -> Result Result<(), String> { @@ -240,3 +245,31 @@ fn reply(ws: &mut Ws, limits: &NetLimits, pick: impl Fn(ServerMsg) -> Option< }; } } + +#[cfg(test)] +mod tests { + use super::*; + + /// A server that keeps pinging and never answers the close must not pin `close` forever. + #[test] + fn close_returns_against_a_server_that_keeps_pinging() { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + std::thread::spawn(move || { + let (s, _) = listener.accept().unwrap(); + let Ok(mut ws) = tungstenite::accept(s) else { return }; + while ws.send(Message::Ping(Bytes::new())).is_ok() { + std::thread::sleep(Duration::from_millis(10)); + } + }); + let limits = NetLimits { idle_timeout: Duration::from_millis(300), ..NetLimits::default() }; + let server = Endpoint { host: addr.to_string(), path: "/".into(), secure: false }; + let ws = connect(&server, &limits, Duration::from_secs(2)).unwrap(); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + close(ws); + let _ = tx.send(()); + }); + assert!(rx.recv_timeout(Duration::from_secs(3)).is_ok(), "close() kept draining a pinging server"); + } +}