fix: client sends Hangup before disconnect, relay handles timeouts gracefully

Client: sends SignalMessage::Hangup(Normal) before closing in all modes
(send-tone, file mode, silence mode) so the relay knows the session ended.

Relay: downgrades "timed out" / "reset" / "closed" recv errors from
ERROR to INFO since these are normal disconnect scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-28 15:15:47 +04:00
parent 4d2c9838c5
commit 6310864b0b
2 changed files with 16 additions and 4 deletions

View File

@@ -359,6 +359,10 @@ async fn run_silence(transport: Arc<wzp_transport::QuinnTransport>) -> anyhow::R
} }
info!(total_source, total_repair, total_bytes, "done — closing"); info!(total_source, total_repair, total_bytes, "done — closing");
let hangup = wzp_proto::SignalMessage::Hangup {
reason: wzp_proto::HangupReason::Normal,
};
transport.send_signal(&hangup).await.ok();
transport.close().await?; transport.close().await?;
Ok(()) Ok(())
} }
@@ -505,14 +509,17 @@ async fn run_file_mode(
// Wait for send to finish (or ctrl+c in recv) // Wait for send to finish (or ctrl+c in recv)
let _ = send_handle.await; let _ = send_handle.await;
// If send finished but recv is still going, give it a moment then stop // Send Hangup signal so the relay knows we're done
let hangup = wzp_proto::SignalMessage::Hangup {
reason: wzp_proto::HangupReason::Normal,
};
transport.send_signal(&hangup).await.ok();
let all_pcm = if record_file.is_some() { let all_pcm = if record_file.is_some() {
// Wait a bit for remaining packets after sender finishes
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
transport.close().await?; transport.close().await?;
recv_handle.await.unwrap_or_default() recv_handle.await.unwrap_or_default()
} else { } else {
// No recording — just close and exit
transport.close().await?; transport.close().await?;
recv_handle.abort(); recv_handle.abort();
Vec::new() Vec::new()

View File

@@ -281,7 +281,12 @@ async fn run_participant_plain(
break; break;
} }
Err(e) => { Err(e) => {
let msg = e.to_string();
if msg.contains("timed out") || msg.contains("reset") || msg.contains("closed") {
info!(%addr, participant = participant_id, "connection closed: {e}");
} else {
error!(%addr, participant = participant_id, "recv error: {e}"); error!(%addr, participant = participant_id, "recv error: {e}");
}
break; break;
} }
}; };