mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-02 00:37:37 +00:00
retry_rename also retries ERROR_SHARING_VIOLATION (32) and ERROR_LOCK_VIOLATION (33), which a rename can report while a scanner holds the file, and caps each pause at 200 ms.
Craft Atomic
craft-atomic provides crash-safe atomic file writing and replacement for the Craft suite.
Extracted from the common storage reliability engine in PhotoCraft, VectorCraft, and FilmCraft (refs #2, #14), it guarantees that a failed save or abnormal termination never leaves user documents truncated, corrupted, or missing.
Core Guarantees
- Non-Destructive Replacement:
atomic_writenever truncates the destination file in place. It writes a temporary file in the same directory as the destination, flushes it to non-volatile storage, renames it over the destination atomically, and flushes the directory so the rename survives power failure. - Guaranteed Same-Directory Staging: Staging files are allocated beside the destination (
.{stem}.{pid}-{n}.craft_tmp), guaranteeing same-filesystem volume rename semantics and eliminatingEXDEV/ERROR_NOT_SAME_DEVICE. - Transient Lock Backoff (Windows): Antivirus scanners and search indexers hold newly written files momentarily.
RenameRetryautomatically retries renames encounteringPermissionDeniedwith exponential backoff. - Apple SMB / Network Share Fallback: On macOS,
sync_allissuesF_FULLFSYNC. Where network shares or virtual file systems reject full flush withENOTSUP(45) orENOTTY(25), it falls back to standardfsync. - Permission Preservation: On Unix, replaces files while preserving the original file's mode and permissions (e.g.
0600). - Fault-Injection Testability: The
AtomicFstrait enables unit and integration test suites to simulate disk-full, locked files, and permission failures without physical disk I/O.
Usage
One-Shot Atomic Write
use std::path::Path;
use craft_atomic::atomic_write;
let path = Path::new("project.craft");
let data = b"precious document state";
// Atomically replaces project.craft. If writing fails, project.craft retains
// its prior bytes and all temporary staging files are unlinked.
atomic_write(path, data).expect("safe atomic write");
Streaming Atomic Writer
use std::io::Write;
use std::path::Path;
use craft_atomic::atomic_write_with;
let path = Path::new("large_project.craft");
atomic_write_with(path, |file| {
file.write_all(b"header...")?;
file.write_all(b"payload...")?;
Ok(())
}).expect("streaming atomic write");
Exclusive Creation (write_new)
use std::path::Path;
use craft_atomic::write_new;
let path = Path::new("export.png");
// Writes only if the destination does not already exist; fails with AlreadyExists
// without modifying existing content.
write_new(path, b"png bytes...").expect("exclusive write");
Fault Injection & Testing
use craft_atomic::{AtomicFs, RenameRetry, atomic_write_custom};
// Inject simulated failures (e.g. storage full mid-stream)
struct FaultyFs;
impl AtomicFs for FaultyFs {
// ... custom failure simulation ...
}
Guarantees & Doctrines
- Zero App Code: Zero dependencies on application crates.
- Never Crash:
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]. - Forbid Unsafe:
#![forbid(unsafe_code)].