Skip to main content

spec_driven_docs/
transaction.rs

1//! Recoverable multi-file writes.
2//!
3//! Three primitives, used first by the user-scope skill installer and
4//! reused by the repository apply. A lock serializes writers over one
5//! record. A stage writes every intended byte beside its destination and
6//! replaces it by rename. A journal names every destination before the
7//! first replacement, so a run the process did not finish is rolled back by
8//! the next invocation rather than left half-applied.
9//!
10//! What this guarantees, and what it does not. Each replacement is atomic
11//! on its own; the set is not. A process that dies at any boundary of the
12//! persistence order below leaves a journal, and the next invocation
13//! restores every destination before it plans new work. Recovery after
14//! power loss rests on that order and on the platform's `fsync` semantics,
15//! and is claimed no further.
16//!
17//! The persistence order is fixed and the same for every domain. Every
18//! backup copy is written and synced. The journal is written and synced,
19//! and its directory synced. Each scratch file is written and synced, then
20//! renamed over its destination, then the destination's directory synced.
21//! Each journal state change is a rewrite through the same pair. The record
22//! is replaced last. The journal is unlinked and its directory synced.
23
24pub mod journal;
25pub mod lock;
26pub mod stage;
27
28use camino::Utf8Path;
29
30/// Flush a directory entry, so a rename into it survives a crash.
31///
32/// A platform that refuses to sync a directory reports `InvalidInput`, and
33/// there the ordering rests on the platform's own semantics rather than on
34/// a call this tool can make.
35///
36/// # Errors
37///
38/// Any I/O error other than a refusal to sync a directory at all.
39pub fn sync_dir(dir: &Utf8Path) -> std::io::Result<()> {
40    match std::fs::File::open(dir).and_then(|handle| handle.sync_all()) {
41        Err(error) if error.kind() == std::io::ErrorKind::InvalidInput => Ok(()),
42        other => other,
43    }
44}
45
46/// Flush the directory holding `path`, where it has one.
47///
48/// # Errors
49///
50/// Any I/O error other than a refusal to sync a directory at all.
51pub fn sync_parent(path: &Utf8Path) -> std::io::Result<()> {
52    match path.parent() {
53        Some(parent) if !parent.as_str().is_empty() => sync_dir(parent),
54        _ => Ok(()),
55    }
56}