spec_driven_docs/transaction.rs
1//! Multi-file writes, one file at a time.
2//!
3//! Two primitives, used by the user-scope skill installer and by the
4//! repository landing. A lock serializes writers over one target. A stage
5//! writes every intended byte beside its destination and replaces it by
6//! rename.
7//!
8//! What this guarantees, and what it does not. Each replacement is atomic
9//! on its own; the set is not. A run the process does not finish leaves
10//! whole files and the record the previous run wrote, and running it again
11//! finishes the rest. Recovery after power loss rests on the persistence
12//! order below and on the platform's `fsync` semantics, and is claimed no
13//! further.
14//!
15//! The persistence order is fixed and the same for every domain. Each
16//! scratch file is written and synced, then renamed over its destination,
17//! then the destination's directory synced. The record is replaced last.
18
19pub mod lock;
20pub mod stage;
21
22use camino::Utf8Path;
23
24/// Flush a directory entry, so a rename into it survives a crash.
25///
26/// A platform that refuses to sync a directory reports `InvalidInput`, and
27/// there the ordering rests on the platform's own semantics rather than on
28/// a call this tool can make.
29///
30/// # Errors
31///
32/// Any I/O error other than a refusal to sync a directory at all.
33pub fn sync_dir(dir: &Utf8Path) -> std::io::Result<()> {
34 match std::fs::File::open(dir).and_then(|handle| handle.sync_all()) {
35 Err(error) if error.kind() == std::io::ErrorKind::InvalidInput => Ok(()),
36 other => other,
37 }
38}
39
40/// Flush the directory holding `path`, where it has one.
41///
42/// # Errors
43///
44/// Any I/O error other than a refusal to sync a directory at all.
45pub fn sync_parent(path: &Utf8Path) -> std::io::Result<()> {
46 match path.parent() {
47 Some(parent) if !parent.as_str().is_empty() => sync_dir(parent),
48 _ => Ok(()),
49 }
50}