Make restart reconnect persistent
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled

This commit is contained in:
DuProcess
2026-06-21 10:51:12 -04:00
parent a029564d80
commit fcc21c7aef
6 changed files with 118 additions and 55 deletions
+21 -15
View File
@@ -203,21 +203,31 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
let retransmit_state = Arc::clone(&state);
let retransmit_socket = Arc::clone(&socket);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_millis(20));
let mut interval = tokio::time::interval(Duration::from_millis(100));
loop {
interval.tick().await;
if let Err(err) = retransmit_pending(&retransmit_state, &retransmit_socket).await {
eprintln!("retransmit error: {err:#}");
}
if let Err(err) = flush_paced_snapshots(&retransmit_state, &retransmit_socket).await {
eprintln!("paced snapshot error: {err:#}");
}
if let Err(err) = maybe_rekey_clients(&retransmit_state, &retransmit_socket).await {
eprintln!("rekey error: {err:#}");
}
}
});
let pacing_state = Arc::clone(&state);
let pacing_socket = Arc::clone(&socket);
let pacing_interval_ms = config.output_frame_interval_ms.max(1);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_millis(pacing_interval_ms));
loop {
interval.tick().await;
if let Err(err) = flush_paced_snapshots(&pacing_state, &pacing_socket).await {
eprintln!("paced snapshot error: {err:#}");
}
}
});
let cleanup_state = Arc::clone(&state);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(5));
@@ -491,16 +501,12 @@ impl ServerState {
/// Whether a session named `name` should be backed by a persistent holder.
///
/// Persistence is only worthwhile for sessions a user can actually reattach
/// to by name: prewarmed sessions and explicitly-named ones (`--session`).
/// Implicit `term-<millis>-<pid>` names (from `dosh host` with no session)
/// are ephemeral — persisting them just leaves unreattachable holders
/// lingering across restarts, so they use the in-process (dies-on-restart)
/// shell that reaps normally on disconnect.
fn should_persist_session(&self, name: &str) -> bool {
/// With persistence enabled, every PTY session gets a detached holder. That
/// includes implicit `term-<millis>-<pid>` sessions: a reconnecting client has
/// the exact session name in its ticket cache, so it can reattach after a
/// quick server restart without forcing users to name sessions manually.
fn should_persist_session(&self, _name: &str) -> bool {
self.config.persist_sessions
&& (self.config.prewarm_sessions.iter().any(|s| s == name)
|| !protocol::is_implicit_session_name(name))
}
/// Spawn (or, in the persistent case, spawn-and-adopt) a PTY for a session.
@@ -3209,7 +3215,7 @@ mod tests {
use super::*;
#[test]
fn persists_named_and_prewarmed_but_not_implicit_sessions() {
fn persists_all_sessions_when_enabled() {
let (pty_tx, _rx) = mpsc::unbounded_channel();
let config = ServerConfig {
persist_sessions: true,
@@ -3219,7 +3225,7 @@ mod tests {
let state = ServerState::new(config, [0u8; 32], pty_tx);
assert!(state.should_persist_session("default")); // prewarmed
assert!(state.should_persist_session("work")); // explicitly named
assert!(!state.should_persist_session("term-1781470634216-76685")); // implicit
assert!(state.should_persist_session("term-1781470634216-76685")); // implicit reconnect
}
#[test]