Skip to main content

AtomicFile

Struct AtomicFile 

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

A scratch file next to an eventual destination, deleted unless it is persisted.

Gives a write its all-or-nothing behaviour: the file is built here and only takes the destination’s name once it is complete. Any earlier return drops the guard, which removes the partial file.

The destination is fixed at create and the scratch path is a sibling of it, so publishing stays on one filesystem: never EXDEV, and atomic on POSIX. (Windows documents no atomicity guarantee when the rename replaces an existing file.)

commit replaces the destination rather than truncating it. Its permission bits are carried over on Unix, but its ownership and hard links cannot be: the published file is a new inode. commit_new never replaces anything and fails if the destination exists.

§Example

let destination = std::path::Path::new("model.myfmt");

// The handle is dropped here because this writer opens the path itself; keep it instead
// when you mean to write through it.
let (scratch, _reserved) = AtomicFile::create(destination)?;
write_my_format(scratch.path())?;
scratch.commit()?;

Implementations§

Source§

impl AtomicFile

Source

pub fn create(destination: &Path) -> Result<(Self, File), Error>

Create a scratch file alongside destination and hand back a handle onto it.

The guard and its file are handed back together so neither can exist without the other: no path is reserved that nothing will clean up, and no file is created that no guard is watching. A caller that writes through its own handle (because the library doing the writing opens the path itself) can drop the returned one; the exclusive create has already done its job by then.

The guard keeps destination, so commit and commit_new can only publish to the path the scratch file was placed beside. That is what makes the same-filesystem publish a property of the type rather than a contract the caller has to honour.

The file is opened with create_new, which refuses a path that already exists and does not follow a symlink planted there - a plain File::create would truncate whatever such a link points at. The scratch name is predictable (pid + counter), so a stale leftover from a killed run, pid reuse, or a deliberately pre-created link all surface as AlreadyExists, answered by moving on to the next counter value.

Source

pub fn path(&self) -> &Path

Where to write. Nothing may be written to the destination itself until commit or commit_new.

Source

pub fn commit(self) -> Result<(), Error>

Flush the completed file to disk and move it onto the destination.

For a caller that wrote through a handle it opened itself and has since closed. The scratch file is reopened here only to sync it, which is what orders the data before the rename: a crash must not leave the destination pointing at bytes that never reached the platter. The reopen asks for write access even though nothing is written through it, because Windows backs sync_all with FlushFileBuffers, which returns Access is denied on a read-only handle.

A caller that still holds the handle it wrote through should sync and drop that handle first: a deferred write error (NFS over quota, a failing disk) is reported to the handle that wrote the bytes and is lost once that handle is closed, so the sync here cannot stand in for it.

Source

pub fn commit_new(self) -> Result<(), Error>

Like commit, but refuses to replace an existing destination.

Returns Error::AlreadyExists if anything is at the destination when the file is published, and the guard removes the scratch file. Checking for the destination beforehand cannot give this guarantee: another writer can create it between the check and the rename, which would then silently replace it.

The file is published with a hard link, which refuses an existing destination in the same call that creates it. On filesystems without hard links (FAT, exFAT, some network mounts) this falls back to a check followed by a rename, which leaves a short window.

Trait Implementations§

Source§

impl Drop for AtomicFile

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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, !>

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.