From 4a41de72ec9612368cc3145abb7b09ebaca0f647 Mon Sep 17 00:00:00 2001 From: CypherNaugh_0x <9931495+CypherNaught-0x@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:51:18 +0200 Subject: [PATCH] Autosave ids are unique within the process: an app restarted in the same second (the scenario harness crashes and restarts in one process) took the id `{seconds}-{pid}` of the crashed app, locked its entry again and so offered nothing to recover. The id now carries a per-process sequence number. Test: a restart in the same second still offers the crashed app's work --- crates/engine/src/recovery.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/recovery.rs b/crates/engine/src/recovery.rs index 2157d528..9f316c55 100644 --- a/crates/engine/src/recovery.rs +++ b/crates/engine/src/recovery.rs @@ -140,7 +140,11 @@ impl Autosaver { let _ = fs::remove_file(&p); } } - let id = format!("{}-{}", unix_now(), std::process::id()); + // Unique within the process too: an app started again in the same second (tests that + // "crash" and restart) must not take over the crashed app's entry. + static SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); + let seq = SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let id = format!("{}-{}-{seq}", unix_now(), std::process::id()); let lock = OpenOptions::new().create(true).truncate(false).write(true).open(file(dir, &id, "lock")).map_err(io)?; lock.lock().map_err(io)?; Ok(Autosaver { dir: dir.to_path_buf(), id, interval, lock: Some(lock), last: None, last_at: Instant::now(), has_entry: false }) @@ -225,6 +229,21 @@ mod tests { let _ = fs::remove_dir_all(&d); } + #[test] + fn an_app_restarted_in_the_same_second_still_offers_the_crashed_ones_work() { + let d = dir("restart"); + let mut s = Session::default(); + let mut a = Autosaver::new(&d, Duration::from_secs(3600)).unwrap(); + s.execute("solid.box", &json!({"length": 10, "width": 10, "height": 10})).unwrap(); + assert!(a.save(&s).unwrap()); + drop(a); + let b = Autosaver::new(&d, Duration::from_secs(3600)).unwrap(); + let found = orphans(&d); + assert_eq!(found.len(), 1, "the crashed app's entry is offered"); + assert_ne!(found[0].id, b.id); + let _ = fs::remove_dir_all(&d); + } + #[test] fn saving_drops_the_entry_and_a_clean_close_leaves_nothing() { let d = dir("clean");