From a4f86afb6afa2001e982c37727da07aaa5e24e5e Mon Sep 17 00:00:00 2001 From: Ofek Gabay <61761153+tupe12334@users.noreply.github.com> Date: Mon, 12 Oct 2026 05:02:45 +0300 Subject: [PATCH] refactor(state): use tempfile for atomic state.json writes Replace the hand-rolled PID-suffixed temp file + rename in save_state with tempfile::NamedTempFile::persist. Temp file names are now unique per call and the temp file is removed when a write fails. Co-Authored-By: Claude Opus 5.5 --- core/Cargo.toml | 2 +- core/src/state.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 8111cd8..789630e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -99,6 +99,7 @@ polyhook = "0.1.12" same-file = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" +tempfile = "3" thiserror = "2" toml = "1.1" uuid = { version = "1", features = ["v4"] } @@ -114,4 +115,3 @@ strip = "symbols" [dev-dependencies] proptest = "1" -tempfile = "3" diff --git a/core/src/state.rs b/core/src/state.rs index 57915a1..e352884 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -1,8 +1,8 @@ //! Session state persistence and core domain types. use std::collections::HashMap; use std::fs; +use std::io::Write; use std::path::Path; -use std::process; use serde::{Deserialize, Serialize}; @@ -48,17 +48,18 @@ pub fn load_state(path: &Path) -> Result { Ok(serde_json::from_str(&content)?) } -/// Atomically write `state` to `path` via a PID-suffixed temp file and POSIX `mv`. +/// Atomically write `state` to `path` via a uniquely named temp file in the same +/// directory, renamed over `path`. The temp file is removed if any step fails. /// /// # Errors /// /// Returns an error if serialization fails or the write or rename fails. pub fn save_state(path: &Path, state: &SessionState) -> Result<()> { let dir = path.parent().unwrap_or_else(|| Path::new(".")); - let tmp = dir.join(format!("state.json.tmp.{}", process::id())); let content = serde_json::to_string_pretty(state)?; - fs::write(&tmp, content)?; - fs::rename(&tmp, path)?; + let mut tmp = tempfile::NamedTempFile::new_in(dir)?; + tmp.write_all(content.as_bytes())?; + tmp.persist(path).map_err(|e| e.error)?; Ok(()) }