#[cfg(unix)]
use std::fs;
use std::io;
use std::path::Path;
#[derive(Debug)]
pub struct InstanceLock {
#[cfg(unix)]
#[expect(dead_code, reason = "the open file is the lock; closing it releases it")]
file: fs::File,
}
impl InstanceLock {
pub fn shared(path: &Path) -> io::Result<Self> {
shared(path)
}
pub fn try_exclusive(path: &Path) -> io::Result<Option<Self>> {
try_exclusive(path)
}
pub fn wait_exclusive(path: &Path) -> io::Result<Self> {
wait_exclusive(path)
}
}
#[cfg(unix)]
fn open_and_lock(path: &Path, operation: rustix::fs::FlockOperation) -> io::Result<InstanceLock> {
let file = fs::OpenOptions::new().read(true).write(true).create(true).truncate(false).open(path)?;
loop {
match rustix::fs::flock(&file, operation) {
Ok(()) => return Ok(InstanceLock { file }),
Err(rustix::io::Errno::INTR) => {}
Err(errno) => return Err(errno.into()),
}
}
}
#[cfg(unix)]
fn shared(path: &Path) -> io::Result<InstanceLock> {
open_and_lock(path, rustix::fs::FlockOperation::LockShared)
}
#[cfg(unix)]
fn try_exclusive(path: &Path) -> io::Result<Option<InstanceLock>> {
match open_and_lock(path, rustix::fs::FlockOperation::NonBlockingLockExclusive) {
Ok(lock) => Ok(Some(lock)),
Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(error) => Err(error),
}
}
#[cfg(unix)]
fn wait_exclusive(path: &Path) -> io::Result<InstanceLock> {
open_and_lock(path, rustix::fs::FlockOperation::LockExclusive)
}
#[cfg(not(unix))]
fn unsupported() -> io::Error {
io::Error::new(io::ErrorKind::Unsupported, "this platform has no advisory lock in this framework; see InstanceLock")
}
#[cfg(not(unix))]
fn shared(_path: &Path) -> io::Result<InstanceLock> {
Err(unsupported())
}
#[cfg(not(unix))]
fn try_exclusive(_path: &Path) -> io::Result<Option<InstanceLock>> {
Err(unsupported())
}
#[cfg(not(unix))]
fn wait_exclusive(_path: &Path) -> io::Result<InstanceLock> {
Err(unsupported())
}
#[cfg(test)]
#[path = "instance_lock_tests.rs"]
mod tests;