diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 98e22455c1..aec0527a9b 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -1000,7 +1000,10 @@ pub fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Res use std::fs::File; use std::io::Write; - let mut file = File::create(main_path)?; + // Write to a temp file in the same directory then atomically rename, so that + // concurrent readers never observe a partially-written file. + let tmp_path = format!("{}.tmp.{}", main_path, Uuid::new_v4()); + let mut file = File::create(&tmp_path)?; file.write_all(byts)?; #[cfg(unix)] { @@ -1009,6 +1012,11 @@ pub fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Res file.set_permissions(Permissions::from_mode(0o755))?; } file.flush()?; + drop(file); + if let Err(e) = std::fs::rename(&tmp_path, main_path) { + let _ = std::fs::remove_file(&tmp_path); + return Err(e.into()); + } Ok(()) } diff --git a/backend/windmill-worker/src/global_cache.rs b/backend/windmill-worker/src/global_cache.rs index 67a8484b82..6dd6429309 100644 --- a/backend/windmill-worker/src/global_cache.rs +++ b/backend/windmill-worker/src/global_cache.rs @@ -273,7 +273,14 @@ pub async fn save_cache( &PathBuf::from(local_cache_path), )?; } else { - std::fs::copy(origin, local_cache_path)?; + // Copy to a temp file in the same directory then atomically rename, so that + // a concurrent `load_cache` never sees a partially-written file via metadata(). + let tmp_path = format!("{}.tmp.{}", local_cache_path, uuid::Uuid::new_v4()); + std::fs::copy(origin, &tmp_path)?; + if let Err(e) = std::fs::rename(&tmp_path, local_cache_path) { + let _ = std::fs::remove_file(&tmp_path); + return Err(e.into()); + } } Ok(format!( "\nwrote cached binary: {} (backed by EE distributed object store: {_cached_to_s3})\n",