use std::fs::{File, OpenOptions};
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};
use directories::ProjectDirs;
use fs4::fs_std::FileExt;
use crate::constants::{CLI_LOCK_POLL_INTERVAL_MS, MAX_CONCURRENT_CLI_INSTANCES};
use crate::errors::AppError;
fn slot_path(slot: usize) -> Result<PathBuf, AppError> {
let cache = if let Some(override_dir) = std::env::var_os("SQLITE_GRAPHRAG_CACHE_DIR") {
PathBuf::from(override_dir)
} else {
let dirs = ProjectDirs::from("", "", "sqlite-graphrag").ok_or_else(|| {
AppError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"não foi possível determinar o diretório de cache para os lock files do sqlite-graphrag",
))
})?;
dirs.cache_dir().to_path_buf()
};
std::fs::create_dir_all(&cache)?;
Ok(cache.join(format!("cli-slot-{slot}.lock")))
}
fn try_acquire_slot(slot: usize) -> Result<File, AppError> {
let path = slot_path(slot)?;
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&path)?;
file.try_lock_exclusive().map_err(AppError::Io)?;
Ok(file)
}
pub fn acquire_cli_slot(
max_concurrency: usize,
wait_seconds: Option<u64>,
) -> Result<(File, usize), AppError> {
let max = max_concurrency.clamp(1, MAX_CONCURRENT_CLI_INSTANCES);
let wait_secs = wait_seconds.unwrap_or(0);
if let Some((file, slot)) = try_any_slot(max)? {
return Ok((file, slot));
}
if wait_secs == 0 {
return Err(AppError::AllSlotsFull {
max,
waited_secs: 0,
});
}
let deadline = Instant::now() + Duration::from_secs(wait_secs);
loop {
thread::sleep(Duration::from_millis(CLI_LOCK_POLL_INTERVAL_MS));
if let Some((file, slot)) = try_any_slot(max)? {
return Ok((file, slot));
}
if Instant::now() >= deadline {
return Err(AppError::AllSlotsFull {
max,
waited_secs: wait_secs,
});
}
}
}
fn try_any_slot(max: usize) -> Result<Option<(File, usize)>, AppError> {
for slot in 1..=max {
match try_acquire_slot(slot) {
Ok(file) => return Ok(Some((file, slot))),
Err(AppError::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => return Err(e),
}
}
Ok(None)
}