Files
tsunagi/crates/tsunagi-core/src/storage/lock.rs
T
Aleksandr BogomiakovandClaude 9966b50407 Rename the CLI package to tsunagi and its executable to tsng
The command line package is now `tsunagi` and builds the `tsng` executable,
which the packages, the unit, the installers and the workflows install and
run. The core library package becomes `tsunagi-core`, keeping the library name
`tsunagi`, so no code changes `use tsunagi::...`. Directories follow
(crates/tsunagi-core, crates/tsunagi). Every command shown to a person is now
`tsng ...`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-03 02:14:43 +01:00

65 lines
2.0 KiB
Rust

//! Ownership lock for a state directory.
//!
//! One persistent state directory belongs to exactly one live agent instance.
//! Checking whether a file exists is not enough — a stale file from a crashed
//! process must not block a restart, and two concurrently starting agents must
//! not both win. An advisory OS file lock gives both properties.
use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
use fs4::{FileExt, TryLockError};
use crate::error::{Error, Result};
/// An exclusive lock held for the lifetime of an agent.
///
/// Dropping it releases the lock, so a cleanly stopped agent leaves the
/// directory immediately reopenable.
#[derive(Debug)]
pub struct DirectoryLock {
file: File,
path: PathBuf,
}
impl DirectoryLock {
/// Acquires the lock, failing fast if another live agent holds it.
pub fn acquire(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref().to_path_buf();
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(&path)
.map_err(|source| Error::Io {
path: path.clone(),
source,
})?;
super::restrict_permissions(&file, &path)?;
match FileExt::try_lock(&file) {
Ok(()) => Ok(Self { file, path }),
Err(TryLockError::WouldBlock) => Err(Error::StateLocked {
path: path
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| path.clone()),
}),
Err(TryLockError::Error(source)) => Err(Error::Io { path, source }),
}
}
/// The path of the lock file.
pub fn path(&self) -> &Path {
&self.path
}
}
impl Drop for DirectoryLock {
fn drop(&mut self) {
// Best effort: the OS releases the lock when the descriptor closes anyway.
let _ = FileExt::unlock(&self.file);
}
}