feat: establish standalone Argand Site Registry
This commit is contained in:
commit
2a0fe1714b
60 changed files with 10494 additions and 0 deletions
16
crates/argand-atomic/Cargo.toml
Normal file
16
crates/argand-atomic/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# engine/crates/argand-atomic/Cargo.toml
|
||||
# By Nic Weyand!
|
||||
|
||||
[package]
|
||||
name = "argand-atomic"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
211
crates/argand-atomic/src/lib.rs
Normal file
211
crates/argand-atomic/src/lib.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// engine/crates/argand-atomic/src/lib.rs
|
||||
// By Nic Weyand!
|
||||
|
||||
//! Power-loss-durable atomic publication of small files.
|
||||
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
static TEMPORARY_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Replaces `path` atomically after its new contents and parent entry are durable.
|
||||
///
|
||||
/// A process-unique sibling prevents concurrent writers from corrupting each
|
||||
/// other's temporary file. Publication remains last-writer-wins.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns the underlying filesystem error when the temporary file cannot be
|
||||
/// created, written, synchronized, renamed, or made durable in its directory.
|
||||
pub fn replace_durable(path: &Path, bytes: &[u8]) -> io::Result<()> {
|
||||
replace_durable_with(path, |file| file.write_all(bytes))
|
||||
}
|
||||
|
||||
/// Replaces `path` atomically with contents written incrementally by `write`.
|
||||
///
|
||||
/// This preserves the same power-loss boundary as [`replace_durable`] without
|
||||
/// requiring a large generated artifact to exist twice in memory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns the writer's error or an underlying filesystem error when the
|
||||
/// temporary file cannot be created, synchronized, renamed, or made durable.
|
||||
pub fn replace_durable_with(
|
||||
path: &Path,
|
||||
write: impl FnOnce(&mut File) -> io::Result<()>,
|
||||
) -> io::Result<()> {
|
||||
let temporary = temporary_sibling(path);
|
||||
let mut guard = TemporaryFile::create(&temporary)?;
|
||||
let file = guard
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or_else(|| io::Error::other("atomic publication temporary file closed unexpectedly"))?;
|
||||
write(file)?;
|
||||
file.sync_all()?;
|
||||
drop(guard.file.take());
|
||||
std::fs::rename(&temporary, path)?;
|
||||
guard.published = true;
|
||||
sync_parent(path)
|
||||
}
|
||||
|
||||
/// Creates `path` atomically after its contents are durable, refusing replacement.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`io::ErrorKind::AlreadyExists`] if `path` already exists. Other
|
||||
/// errors describe temporary-file creation, writing, synchronization,
|
||||
/// publication, or parent-directory synchronization failures.
|
||||
pub fn create_durable(path: &Path, bytes: &[u8]) -> io::Result<()> {
|
||||
create_durable_with(path, |file| file.write_all(bytes))
|
||||
}
|
||||
|
||||
/// Creates `path` atomically from incrementally written contents, without clobbering.
|
||||
///
|
||||
/// The complete synchronized sibling is published with a hard link, so readers
|
||||
/// can never observe partial contents and an existing destination wins.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`io::ErrorKind::AlreadyExists`] if `path` already exists. Other
|
||||
/// errors describe temporary-file creation, writing, synchronization,
|
||||
/// publication, or parent-directory synchronization failures.
|
||||
pub fn create_durable_with(
|
||||
path: &Path,
|
||||
write: impl FnOnce(&mut File) -> io::Result<()>,
|
||||
) -> io::Result<()> {
|
||||
let temporary = temporary_sibling(path);
|
||||
let mut guard = TemporaryFile::create(&temporary)?;
|
||||
let file = guard
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or_else(|| io::Error::other("atomic publication temporary file closed unexpectedly"))?;
|
||||
write(file)?;
|
||||
file.sync_all()?;
|
||||
drop(guard.file.take());
|
||||
std::fs::hard_link(&temporary, path)?;
|
||||
std::fs::remove_file(&temporary)?;
|
||||
guard.published = true;
|
||||
sync_parent(path)
|
||||
}
|
||||
|
||||
fn temporary_sibling(path: &Path) -> PathBuf {
|
||||
let sequence = TEMPORARY_SEQUENCE.fetch_add(1, Ordering::Relaxed);
|
||||
let mut name = path.file_name().unwrap_or_default().to_os_string();
|
||||
name.push(format!(".{}.{}.tmp", std::process::id(), sequence));
|
||||
path.with_file_name(name)
|
||||
}
|
||||
|
||||
fn sync_parent(path: &Path) -> io::Result<()> {
|
||||
let parent = path
|
||||
.parent()
|
||||
.filter(|parent| !parent.as_os_str().is_empty())
|
||||
.unwrap_or_else(|| Path::new("."));
|
||||
File::open(parent)?.sync_all()
|
||||
}
|
||||
|
||||
struct TemporaryFile {
|
||||
file: Option<File>,
|
||||
path: PathBuf,
|
||||
published: bool,
|
||||
}
|
||||
|
||||
impl TemporaryFile {
|
||||
fn create(path: &Path) -> io::Result<Self> {
|
||||
let file = OpenOptions::new() // atomic-writes: allow canonical atomic publication primitive
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(path)?;
|
||||
Ok(Self {
|
||||
file: Some(file),
|
||||
path: path.to_owned(),
|
||||
published: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TemporaryFile {
|
||||
fn drop(&mut self) {
|
||||
if !self.published {
|
||||
let _ = std::fs::remove_file(&self.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
|
||||
use super::{create_durable, create_durable_with, replace_durable, replace_durable_with};
|
||||
|
||||
#[test]
|
||||
fn creates_complete_contents_without_clobbering() -> std::io::Result<()> {
|
||||
let directory = tempfile::tempdir()?;
|
||||
let destination = directory.path().join("receipt.json");
|
||||
|
||||
create_durable(&destination, b"first")?;
|
||||
let error = match create_durable(&destination, b"second") {
|
||||
Ok(()) => return Err(std::io::Error::other("existing destination was clobbered")),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::AlreadyExists);
|
||||
assert_eq!(std::fs::read(&destination)?, b"first");
|
||||
assert_eq!(std::fs::read_dir(directory.path())?.count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_create_removes_unpublished_temporary_file() -> std::io::Result<()> {
|
||||
let directory = tempfile::tempdir()?;
|
||||
let destination = directory.path().join("receipt.json");
|
||||
|
||||
let error = match create_durable_with(&destination, |file| {
|
||||
file.write_all(b"partial")?;
|
||||
Err(std::io::Error::other("fixture failure"))
|
||||
}) {
|
||||
Ok(()) => {
|
||||
return Err(std::io::Error::other(
|
||||
"fixture writer unexpectedly succeeded",
|
||||
));
|
||||
}
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::Other);
|
||||
assert!(!destination.exists());
|
||||
assert_eq!(std::fs::read_dir(directory.path())?.count(), 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_complete_contents_without_temporary_debris() -> std::io::Result<()> {
|
||||
let directory = tempfile::tempdir()?;
|
||||
let destination = directory.path().join("checkpoint.json");
|
||||
|
||||
replace_durable(&destination, b"old")?;
|
||||
replace_durable(&destination, b"complete-new-value")?;
|
||||
|
||||
assert_eq!(std::fs::read(&destination)?, b"complete-new-value");
|
||||
assert_eq!(std::fs::read_dir(directory.path())?.count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn streams_complete_contents_without_temporary_debris() -> std::io::Result<()> {
|
||||
let directory = tempfile::tempdir()?;
|
||||
let destination = directory.path().join("streamed");
|
||||
|
||||
replace_durable_with(&destination, |file| {
|
||||
file.write_all(b"first-")?;
|
||||
file.write_all(b"second")
|
||||
})?;
|
||||
|
||||
assert_eq!(std::fs::read(&destination)?, b"first-second");
|
||||
assert_eq!(std::fs::read_dir(directory.path())?.count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue