use std::collections::{BTreeSet, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use crate::graph::schema::{EdgeData, NodeData};
type Stamped<T> = (u64, Box<T>);
pub(crate) struct QueryArenas {
active: Mutex<BTreeSet<u64>>,
next_id: AtomicU64,
nodes: Mutex<VecDeque<Stamped<NodeData>>>,
edges: Mutex<VecDeque<Stamped<EdgeData>>>,
}
fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
impl QueryArenas {
pub(crate) fn new(capacity: usize) -> Arc<Self> {
Arc::new(Self {
active: Mutex::new(BTreeSet::new()),
next_id: AtomicU64::new(1),
nodes: Mutex::new(VecDeque::with_capacity(capacity)),
edges: Mutex::new(VecDeque::with_capacity(capacity)),
})
}
pub(crate) fn begin(self: &Arc<Self>) -> DiskQueryGuard {
let mut active = lock(&self.active);
if active.is_empty() {
self.clear_records();
}
let id = self.next_id.fetch_add(1, Ordering::AcqRel);
active.insert(id);
DiskQueryGuard {
arenas: Arc::clone(self),
id,
}
}
#[cfg(any(test, debug_assertions))]
pub(crate) fn active_count(&self) -> usize {
lock(&self.active).len()
}
pub(crate) fn push_node(&self, data: NodeData) -> *const NodeData {
let boxed = Box::new(data);
let ptr: *const NodeData = &*boxed;
let epoch = self.next_id.load(Ordering::Acquire);
lock(&self.nodes).push_back((epoch, boxed));
ptr
}
pub(crate) fn push_edge(&self, data: EdgeData) -> *const EdgeData {
let boxed = Box::new(data);
let ptr: *const EdgeData = &*boxed;
let epoch = self.next_id.load(Ordering::Acquire);
lock(&self.edges).push_back((epoch, boxed));
ptr
}
#[cfg(test)]
pub(crate) fn node_len(&self) -> usize {
lock(&self.nodes).len()
}
#[cfg(test)]
pub(crate) fn edge_len(&self) -> usize {
lock(&self.edges).len()
}
pub(crate) fn clear_all(&self) {
let _active = lock(&self.active);
self.clear_records();
}
pub(crate) fn reclaim_if_idle(&self) {
let active = lock(&self.active);
if active.is_empty() {
self.clear_records();
}
}
fn release(&self, id: u64) {
let mut active = lock(&self.active);
let was_oldest = active.first().copied() == Some(id);
let removed = active.remove(&id);
debug_assert!(removed, "DiskQueryGuard released an unregistered query id");
match active.first().copied() {
None => self.clear_records(),
Some(oldest) if was_oldest => self.drain_through(oldest),
_ => {}
}
}
fn drain_through(&self, oldest: u64) {
let mut nodes = lock(&self.nodes);
while nodes.front().is_some_and(|(epoch, _)| *epoch <= oldest) {
nodes.pop_front();
}
drop(nodes);
let mut edges = lock(&self.edges);
while edges.front().is_some_and(|(epoch, _)| *epoch <= oldest) {
edges.pop_front();
}
}
fn clear_records(&self) {
lock(&self.nodes).clear();
lock(&self.edges).clear();
}
}
pub struct DiskQueryGuard {
arenas: Arc<QueryArenas>,
id: u64,
}
impl Drop for DiskQueryGuard {
fn drop(&mut self) {
self.arenas.release(self.id);
}
}