From 9bbaec6b35762beb01dff2373eb06ea461b8384c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 05:20:20 +0000 Subject: [PATCH] fix: use shutdown_timeout so QUIC CONNECTION_CLOSE actually gets sent shutdown_background() killed the tokio runtime before quinn could send the CONNECTION_CLOSE frame on the wire, so the relay never knew the client left. Now use shutdown_timeout(500ms) to give quinn time to flush the close frame, matching the desktop client pattern (which uses 2s timeout). Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/wzp-android/src/engine.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/wzp-android/src/engine.rs b/crates/wzp-android/src/engine.rs index b459bc7..f2bec65 100644 --- a/crates/wzp-android/src/engine.rs +++ b/crates/wzp-android/src/engine.rs @@ -149,14 +149,18 @@ impl WzpEngine { pub fn stop_call(&mut self) { self.state.running.store(false, Ordering::Release); - // Close QUIC connection immediately so the relay detects disconnect - // and removes us from the room (broadcasts RoomUpdate to others). + // Close QUIC connection first — queues a CONNECTION_CLOSE frame. + // Quinn needs the tokio runtime alive to actually send it on the wire, + // so we use shutdown_timeout() to give it time to flush. if let Some(transport) = self.state.quic_transport.lock().unwrap().take() { transport.close_now(); } let _ = self.state.command_tx.send(EngineCommand::Stop); if let Some(rt) = self.tokio_runtime.take() { - rt.shutdown_background(); + // Give quinn up to 500ms to send the CONNECTION_CLOSE frame. + // The desktop client uses 2s, but we keep it short on Android + // to avoid blocking the UI thread. + rt.shutdown_timeout(std::time::Duration::from_millis(500)); } self.call_start = None; }