Skip to main content

Journal

Struct Journal 

Source
pub struct Journal { /* private fields */ }
Expand description

Where a change set’s write-ahead journal lives — and, because they must agree about it, both halves of the protocol that depends on the answer.

The name is a single transient dotfile, by default in the root: it exists only between a change set’s commit point and its completion, so in steady state the tree carries no journal at all, and no dotfolder is spawned to hold one. It survives a crash solely so Journal::recover can find it, and is removed the moment recovery (or a clean apply) finishes.

§Why this is a type and not a parameter

apply and recover have to name the same file. If they disagree, nothing fails loudly — recovery simply looks where no journal is and reports Recovered::Nothing, leaving an interrupted change half-applied forever. A caller that holds one Journal and uses it for both cannot make that mistake, which is why the two operations live on the value rather than taking the name separately.

ChangeSet::apply and the free recover are shorthands for Journal::default(); reach for a named one when the default would collide with something the tree already means, or when an existing deployment already writes a journal under its own name.

let journal = Journal::named(".myapp-journal")?;
assert_eq!(journal.name(), ".myapp-journal");
// Not a single path component — refused rather than escaping the root.
assert!(Journal::named("../elsewhere").is_err());

§A journal outside the tree

By default the journal lands in the root it applies to, which is right for a tree only this machine writes. It is wrong for a tree something syncs — an iCloud or Dropbox folder — because the journal is this process’s crash state, and a sync service cannot tell it from content: the file travels to machines that never crashed, where a recovery would replay another machine’s intent against a tree that may have moved on, and an apply would refuse a “stale” journal no local change left behind. kept_in is the fix: the journal lives in a directory the caller owns and nothing syncs (an application-support or cache directory), and the tree itself never holds a journal at all, transiently or otherwise.

Two obligations come with a homed journal, both the caller’s. The home must be absolute — a relative one would resolve against whatever the process’s current directory happens to be, and a journal written from one directory and sought from another is exactly the stranding this type exists to prevent; kept_in refuses anything else. And the pairing of home and root is not recorded anywhere: the journal does not know which tree it belongs to, so recovering it against a different root replays intent against the wrong tree. One home directory, one root, one name — a caller with several roots keeps several names.

Implementations§

Source§

impl Journal

Source

pub async fn apply<FS: Storage>( &self, changes: &ChangeSet, fs: &FS, root: &Path, ) -> Result<()>

Execute every op changes staged against fs, rooted at root, as one unit — crash-atomically, behind this write-ahead journal.

The set’s intent is journaled and flushed before any document is touched (see crate::journal); that flush is the commit point. From there the ops run in order, each recording how to undo itself:

  • On success, everything the set dirtied is flushed durable — barriers capped by one drain — and only then is the journal removed: Ok means the change survives a power cut, not merely that it happened.
  • On an error (a full disk, a permission fault), every op already applied is unwound in reverse, the restored state is flushed, and the journal is durably cleared — the mutation aborts as if it never began, and a power cut cannot contradict the abort by resurrecting the journal for recovery to roll forward.
  • On a crash (a kill -9, a power cut) there is no error to catch and no chance to unwind, so the journal simply survives; the next crate::journal::recover rolls the set forward to its fully-applied state. An interrupted change set is therefore always resolved to a consistent tree — fully before it on a caught error, fully after it on a crash.

A set of one op skips the journal entirely: a single op is already indivisible on a backend claiming atomic_replace, so there is no multi-file window for a journal to close, and a crash leaves the op either wholly done or wholly not — the same two states a recovered set lands on. It is the ordinary shape of a save, and it costs one file operation rather than four. What it does not skip is durability: a lone rename or remove still flushes the entries it edited before Ok, on the same promise a journaled set keeps.

The rare exception is a rollback that itself fails (Error::Torn): the pre-change state could not be restored, so — rather than leave an unknown one — the journal is kept, and recovery will later roll the set forward to the consistent applied state. Either way the tree lands on a state this crate can name.

Takes fs/root rather than a higher-level object so a bootstrap that must write two files before the tree exists can still land them together.

Whatever recovers an interruption of this call must name the same journal — see Journal for why the two operations live together.

Source§

impl Journal

Source

pub const DEFAULT_NAME: &'static str = ".fstx-journal"

The name used when none is given: a dotted, crate-namespaced file unlikely to collide with anything the tree itself means.

Source

pub fn named(name: impl Into<Cow<'static, str>>) -> Result<Self>

A journal under name, which must be a single path component — not empty, not . or .., and containing no separator.

The check is what keeps the name from being an escape hatch: it is joined onto a caller-supplied root, and a name like ../../elsewhere would write outside the very tree ChangeSet::apply clamps every staged op into.

Source

pub fn kept_in(self, home: impl Into<PathBuf>) -> Result<Self>

This journal, kept in home instead of in the root it applies to.

For trees something syncs — see the type docs for why the journal must not live where a sync service can carry it to another machine, and for the two obligations (an absolute home, and a stable home–root pairing) that come with taking this.

home is a directory; the journal keeps its name inside it. A relative home is refused (Error::InvalidJournalHome): it would resolve against the process’s current directory, which apply and a later recovery have no reason to share.

let journal = Journal::named(".myapp-journal")?
    .kept_in("/var/lib/myapp/journals")?;
assert!(Journal::default().kept_in("not/absolute").is_err());
Source

pub fn name(&self) -> &str

The journal’s file name.

Source

pub fn path_in(&self, root: &Path) -> PathBuf

Where this journal lives when applying to root: its home if it has one, otherwise the root itself.

Source

pub fn owns_path(&self, path: &Path) -> bool

Whether path names this journal, or the staging sibling Storage::write_atomic publishes it through.

A containment test on the file name rather than an equality one, so that both the journal and its transient write_atomic temporary are recognized. A homed journal additionally requires the path to sit in its home: the homed design’s whole point is that no file in the root is this journal, so a same-named file there — synced in, or simply a coincidence — must not be claimed. Used by a fault-injecting backend to leave the journal’s own writes alone and fail only the file writes it means to.

Source§

impl Journal

Source

pub async fn recover<FS: Storage>( &self, fs: &FS, root: &Path, ) -> Result<Recovered>

Finish any change set a crash left journaled at root, rolling the tree forward to the fully-applied state, then remove the journal.

The recovery entry point: run it before anything reads the tree, so an interrupted change heals first. A no-op when no journal is present, so it is cheap to call unconditionally. Replay is idempotent — a write already landed is simply rewritten, a rename already done is recognized and skipped — so recovering the same journal twice (a crash during recovery) is safe.

Must name the same journal the interrupted apply wrote. A different one finds nothing and reports Recovered::Nothing, which is why both live on this value.

Trait Implementations§

Source§

impl Clone for Journal

Source§

fn clone(&self) -> Journal

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Journal

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Journal

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for Journal

Source§

impl PartialEq for Journal

Source§

fn eq(&self, other: &Journal) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Journal

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.