use std::cell::Cell;
use std::fs::{File, OpenOptions};
use std::sync::Mutex;
use anyhow::{Context, Result};
use crate::profile::clauth_dir;
const LOCK_FILENAME: &str = ".lock";
static THREAD_LOCK: Mutex<Option<File>> = Mutex::new(None);
thread_local! {
static DEPTH: Cell<u32> = const { Cell::new(0) };
}
pub(crate) struct StateLock {
_thread_guard: Option<std::sync::MutexGuard<'static, Option<File>>>,
_rank: Option<crate::lockorder::RankGuard>,
}
impl StateLock {
pub(crate) fn acquire() -> Result<Self> {
let depth = DEPTH.get();
if depth > 0 {
DEPTH.set(
depth
.checked_add(1)
.expect("clauth state lock depth overflow"),
);
return Ok(Self {
_thread_guard: None,
_rank: None,
});
}
let mut guard: std::sync::MutexGuard<'static, Option<File>> = match THREAD_LOCK.lock() {
Ok(g) => g,
Err(p) => p.into_inner(),
};
if guard.is_none() {
let dir = clauth_dir()?;
std::fs::create_dir_all(&dir).context("Failed to create ~/.clauth")?;
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(dir.join(LOCK_FILENAME))
.context("Failed to open clauth state lock file")?;
file.lock().context("Failed to acquire clauth state lock")?;
*guard = Some(file);
}
DEPTH.set(1);
let rank = crate::lockorder::RankGuard::enter(crate::lockorder::rank::STATE);
Ok(Self {
_thread_guard: Some(guard),
_rank: Some(rank),
})
}
}
impl Drop for StateLock {
fn drop(&mut self) {
let depth = DEPTH.get();
let new_depth = depth.saturating_sub(1);
DEPTH.set(new_depth);
if new_depth == 0 {
if let Some(ref mut g) = self._thread_guard {
**g = None; }
}
}
}
pub(crate) fn with_state_lock<T>(f: impl FnOnce() -> Result<T>) -> Result<T> {
let _guard = StateLock::acquire()?;
f()
}
#[cfg(test)]
#[path = "../tests/inline/lock.rs"]
mod tests;