fix(daemon): reconnect after the server session drops

A long-running daemon that lost its TCP connection to the server reported
session_loss "disconnected" and answered every command from the dead session
until a manual restart. Two causes:

- the daemon owned one session for its lifetime with no reconnect at all; and
- the lib's ServerActor queued a Login sent while disconnected instead of
  dialing, so even asking it to log in again never re-established a connection.

Reconnect on a dedicated thread with 1s-60s capped backoff, only for a dropped
connection to a session this process owns (never a displaced or borrowed one,
which would start a login war or fight another daemon). Let a Login through the
disconnected guard so a parked actor dials again, and bound that dial with
connect_timeout so a blackholed route cannot park the actor thread. Reset the
message reader, the queued backlog and the login verdict on disconnect so a
partial frame or stale Some(false) cannot wedge the reconnected session.
Announce the drop once per outage and re-join rooms and re-watch users on
recovery; the daemon main loop is back to save-and-stop so `daemon stop` stays
responsive during an outage. Update the session_loss event summary, which no
longer means the session is gone for good.
This commit is contained in:
Michel de Graaf
2026-09-02 11:21:19 +02:00
parent 597f378f17
commit 67d979b809
8 changed files with 436 additions and 63 deletions
+8 -8
View File
@@ -15,7 +15,7 @@ use super::proto::{
SessionLossEvent, UploadInfoDto, UploadStatusDto, UserMessageDto,
};
use crate::api::SessionApi;
use soulseek_rs::DownloadStatus;
use soulseek_rs::{DownloadStatus, SessionLoss};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::mpsc::{Receiver, SyncSender, TrySendError, sync_channel};
@@ -175,9 +175,7 @@ struct DrainState {
/// Last upload state reported per (user, file), so an upload that has not
/// moved is not re-announced every tick.
uploads: HashMap<(String, String), UploadInfoDto>,
/// Whether the session-loss event has already gone out. It is terminal, so
/// it is worth saying exactly once.
reported_loss: bool,
last_loss: Option<SessionLoss>,
}
/// How long to keep asking whether a peer has answered a browse request.
@@ -273,10 +271,12 @@ fn tick(
publish_browses(session, hub, browses);
publish_downloads(hub, downloads);
if !state.reported_loss
&& let Some(loss) = session.session_loss()
{
state.reported_loss = true;
let loss = session.session_loss();
if loss == state.last_loss {
return;
}
state.last_loss = loss;
if let Some(loss) = loss {
hub.publish(
Event::SessionLoss,
serde_json::to_value(SessionLossEvent { loss: loss.into() })