Skip to content

Commit 4f02579

Browse files
committed
fix: once server disconnects, drop the connectino
Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent 1ec76d8 commit 4f02579

1 file changed

Lines changed: 23 additions & 1 deletion

File tree

sandd/src/main.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,17 +324,30 @@ where
324324
.context("Failed to initialize snapshot manager")?,
325325
);
326326

327-
// Spawn heartbeat task
327+
// Spawn heartbeat task. A failed heartbeat send is our ONLY reliable signal
328+
// that the connection is dead: over a DERP-relayed mesh, a controller that
329+
// vanishes (e.g. pod restart) often produces no TCP FIN/RST on the daemon side,
330+
// so `ws_rx.next()` in the loop below blocks forever and never surfaces the
331+
// drop. The heartbeat write, by contrast, fails. So on send failure we trip
332+
// `dead_tx`, which the select! polls to break the serve loop and let main()
333+
// reconnect — without this the daemon wedges half-open until the pod is deleted.
328334
let ws_tx_clone = Arc::new(tokio::sync::Mutex::new(ws_tx));
329335
let ws_tx_heartbeat = ws_tx_clone.clone();
336+
let (dead_tx, dead_rx) = tokio::sync::oneshot::channel::<()>();
330337
let heartbeat_handle = tokio::spawn(async move {
331338
let mut interval = tokio::time::interval(Duration::from_secs(heartbeat_interval));
339+
let mut dead_tx = Some(dead_tx);
332340
loop {
333341
interval.tick().await;
334342
let heartbeat = Message::Heartbeat;
335343
if let Ok(json) = serde_json::to_string(&heartbeat) {
336344
let mut tx = ws_tx_heartbeat.lock().await;
337345
if tx.send(WsMessage::Text(json)).await.is_err() {
346+
// Signal the serve loop that the connection is dead so it
347+
// reconnects instead of blocking forever on a half-open read.
348+
if let Some(d) = dead_tx.take() {
349+
let _ = d.send(());
350+
}
338351
break;
339352
}
340353
}
@@ -350,6 +363,7 @@ where
350363
// Pin the shutdown future once so it can be polled across loop iterations
351364
// without being moved (it may be `!Unpin`).
352365
tokio::pin!(shutdown);
366+
tokio::pin!(dead_rx);
353367
let outcome = loop {
354368
tokio::select! {
355369
// Poll shutdown FIRST. With `biased`, tokio checks branches top to
@@ -371,6 +385,14 @@ where
371385
break ServeOutcome::Shutdown;
372386
}
373387

388+
// Heartbeat send failed => connection is dead. Reconnect. (The Err
389+
// arm — heartbeat task gone without signalling — is treated the same:
390+
// no live heartbeat means no live connection.)
391+
_ = &mut dead_rx => {
392+
warn!("Heartbeat send failed, connection is dead; reconnecting");
393+
break ServeOutcome::Disconnected;
394+
}
395+
374396
msg = ws_rx.next() => {
375397
let msg = match msg {
376398
Some(Ok(WsMessage::Text(text))) => text,

0 commit comments

Comments
 (0)