//! Advisory FIFO locks and cleanup of ended processes. use crate::error; use crate::{Host, RpcResult, host::State}; use serde_json::json; use std::collections::VecDeque; use tokio::sync::oneshot; pub(crate) struct Lock { pub(crate) owner: String, pub(crate) queue: VecDeque<(String, oneshot::Sender)>, } impl Host { /// Acquire a FIFO queue position, or report whether the lock is free. pub(crate) async fn lock(&self, caller: &str, path: &str, wait: bool) -> RpcResult { let rx = { let mut state = self.0.state.lock().unwrap(); if caller != "console" && caller != "core" && !state .processes .get(caller) .is_some_and(|p| p.info.state == "running") { return Err(error(-32600, "process is stopping or ended")); } if let Some(lock) = state.locks.get_mut(path) { if !wait { return Ok(json!({"held":false})); } let (tx, rx) = oneshot::channel(); lock.queue.push_back((caller.into(), tx)); Some(rx) } else { state.locks.insert( path.into(), Lock { owner: caller.into(), queue: VecDeque::new(), }, ); None } }; match rx { Some(rx) => rx .await .unwrap_or_else(|_| Err(error(-32603, "lock canceled"))), None => Ok(json!({"held":true})), } } /// Release a held lock only when this process owns it. pub(crate) fn unlock(&self, caller: &str, path: &str) { let mut state = self.0.state.lock().unwrap(); if state .locks .get(path) .is_some_and(|lock| lock.owner == caller) { grant(&mut state, path); } } /// Remove queued acquisitions and grant the next owner of each held lock. pub(crate) fn release_locks(state: &mut State, pid: &str) { for lock in state.locks.values_mut() { lock.queue.retain(|(p, _)| p != pid); } let paths: Vec<_> = state .locks .iter() .filter(|(_, l)| l.owner == pid) .map(|(p, _)| p.clone()) .collect(); for path in paths { grant(state, &path); } } } pub(crate) fn grant(state: &mut State, path: &str) { if let Some(mut lock) = state.locks.remove(path) { while let Some((owner, tx)) = lock.queue.pop_front() { if tx.send(Ok(json!({"held":true}))).is_ok() { lock.owner = owner; state.locks.insert(path.into(), lock); break; } } } }