Keep sessions alive through UDP churn
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
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:
+32
-1
@@ -23,12 +23,14 @@ use std::io::{Read, Write};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
/// One-byte commands a server sends to a holder over its control socket after
|
||||
/// the holder has handed back the master fd.
|
||||
const HOLDER_CMD_SHUTDOWN: u8 = b'X';
|
||||
const HOLDER_STARTUP_TIMEOUT: Duration = Duration::from_millis(750);
|
||||
static SCREEN_WRITE_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Magic written into a holder's `meta` file, bumped if the on-disk layout
|
||||
/// changes so a stale holder from an incompatible build is ignored.
|
||||
@@ -140,13 +142,28 @@ pub fn save_screen(
|
||||
// No holder runtime for this session (non-persistent): nothing to do.
|
||||
return Ok(());
|
||||
}
|
||||
let _guard = SCREEN_WRITE_LOCK
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("screen persistence lock poisoned"))?;
|
||||
let path = screen_path(&dir);
|
||||
if existing_screen_output_seq(&path).is_some_and(|existing| existing >= output_seq) {
|
||||
return Ok(());
|
||||
}
|
||||
let mut buf = Vec::with_capacity(snapshot.len() + 32);
|
||||
buf.extend_from_slice(&cols.to_be_bytes());
|
||||
buf.extend_from_slice(&rows.to_be_bytes());
|
||||
buf.extend_from_slice(&output_seq.to_be_bytes());
|
||||
buf.extend_from_slice(&(snapshot.len() as u32).to_be_bytes());
|
||||
buf.extend_from_slice(snapshot);
|
||||
atomic_write(&screen_path(&dir), &buf)
|
||||
atomic_write(&path, &buf)
|
||||
}
|
||||
|
||||
fn existing_screen_output_seq(path: &Path) -> Option<u64> {
|
||||
let data = std::fs::read(path).ok()?;
|
||||
if data.len() < 12 {
|
||||
return None;
|
||||
}
|
||||
Some(u64::from_be_bytes(data[4..12].try_into().ok()?))
|
||||
}
|
||||
|
||||
/// A restored screen for a re-adopted session.
|
||||
@@ -619,6 +636,20 @@ mod tests {
|
||||
assert_eq!(loaded.snapshot, snap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_screen_never_overwrites_newer_snapshot_with_older_seq() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sessions_dir = tmp.path();
|
||||
ensure_runtime_dir(sessions_dir, "work").unwrap();
|
||||
|
||||
save_screen(sessions_dir, "work", 80, 24, 10, b"new-screen").unwrap();
|
||||
save_screen(sessions_dir, "work", 80, 24, 9, b"old-screen").unwrap();
|
||||
|
||||
let loaded = load_screen(sessions_dir, "work").expect("screen restored");
|
||||
assert_eq!(loaded.output_seq, 10);
|
||||
assert_eq!(loaded.snapshot, b"new-screen");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_screen_absent_is_none() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user