use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
#[derive(Debug)]
pub(crate) struct SupervisorLock {
_file: File,
path: PathBuf,
}
impl SupervisorLock {
pub(crate) fn acquire(path: &Path) -> Result<Self> {
let directory = path
.parent()
.context("the supervisor lock path has no parent directory")?;
std::fs::create_dir_all(directory).with_context(|| {
format!(
"failed to create the runtime directory {}",
directory.display()
)
})?;
let mut file = OpenOptions::new()
.create(true)
.read(true)
.truncate(false)
.write(true)
.open(path)
.with_context(|| format!("failed to open the supervisor lock {}", path.display()))?;
if crate::supervisor::rendezvous::try_advisory_lock(&file, true).is_err() {
bail!(
"another phoxal-supervisor already owns this bundle's execution (the supervisor \
lock {} is held); attach to it or stop it rather than starting a second \
supervisor",
path.display()
);
}
let _ = file.set_len(0);
let _ = writeln!(file, "{}", std::process::id());
Ok(Self {
_file: file,
path: path.to_path_buf(),
})
}
pub(crate) fn path(&self) -> &Path {
&self.path
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_second_holder_is_refused_and_a_released_lock_is_free_again() {
let dir = tempfile::tempdir().expect("temp dir");
let path = dir.path().join("run").join("supervisor.lock");
let held = SupervisorLock::acquire(&path).expect("the first supervisor takes the lock");
assert_eq!(held.path(), path);
let error = SupervisorLock::acquire(&path).expect_err("a second supervisor is refused");
let rendered = format!("{error:#}");
assert!(rendered.contains("already owns"), "{rendered}");
assert!(rendered.contains("attach to it or stop it"), "{rendered}");
drop(held);
SupervisorLock::acquire(&path).expect("a released lock is free again");
}
}