haematite 0.7.0

Content-addressed, branchable, actor-native storage engine
Documentation
//! FENCE-CHAIN r8 directory-entry barrier bookkeeping: the prefix-subdir
//! establishment (the D1 write-path exception, L3) and the batched barrier that
//! discharges the dirty directory sets through the one fence-chain primitive.

use std::cell::RefCell;
use std::collections::BTreeSet;
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};

use super::construct::directory_error;
use super::error::StoreError;

/// fsync every directory in `set` through the fence-chain primitive, clearing
/// each only after its fsync succeeds and re-retaining the remainder (including
/// the failed one) on the first failure — so a failed barrier is retryable and
/// never silently forgets an un-fenced directory.
pub fn drain_and_fence(set: &RefCell<BTreeSet<PathBuf>>) -> Result<(), StoreError> {
    let dirs = std::mem::take(&mut *set.borrow_mut());
    let mut remaining = dirs.into_iter();
    while let Some(dir) = remaining.next() {
        let synced = crate::fence::sync_dir_entry(&dir);
        if let Err(error) = synced {
            let mut pending = set.borrow_mut();
            pending.insert(dir);
            pending.extend(remaining);
            return Err(StoreError::Io(error));
        }
    }
    Ok(())
}

/// Establish the node's prefix subdirectory a single level below the store root
/// (the D1 write-path exception: content-addressed fan-out cannot be pre-created
/// 256-wide at open without violating the idle-cost lens). Returns whether THIS
/// call created it, so the caller retains the store root for the L3 entry fence.
/// A concurrent creator winning the race (`AlreadyExists`) is accepted — the
/// entry still needs fencing, so the caller retains the store root regardless of
/// which writer's `create_dir` won. In test builds the creation is journalled so
/// a CUT can rewind an unfenced prefix subdir (and the node files beneath it).
pub fn ensure_prefix_dir(prefix_dir: &Path) -> Result<bool, StoreError> {
    match fs::metadata(prefix_dir) {
        Ok(metadata) if metadata.is_dir() => Ok(false),
        Ok(_metadata) => Err(StoreError::NotADirectory {
            path: prefix_dir.to_path_buf(),
        }),
        Err(error) if error.kind() == ErrorKind::NotFound => {
            #[cfg(test)]
            let reservation = crate::fence::journal::reserve_create_dir(prefix_dir);
            match fs::create_dir(prefix_dir) {
                Ok(()) => {
                    #[cfg(test)]
                    reservation.commit();
                    Ok(true)
                }
                Err(error) if error.kind() == ErrorKind::AlreadyExists => {
                    #[cfg(test)]
                    reservation.cancel();
                    Ok(true)
                }
                Err(error) => {
                    #[cfg(test)]
                    reservation.cancel();
                    Err(directory_error(error, prefix_dir))
                }
            }
        }
        Err(error) => Err(directory_error(error, prefix_dir)),
    }
}