Skip to main content

modelvault_core/db/
handle_registry.rs

1//! Process-wide shared database state for same-process read-only views.
2
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, Mutex, OnceLock, RwLock};
6
7use crate::catalog::Catalog;
8use crate::error::DbError;
9use crate::index::IndexState;
10
11use super::LatestMap;
12
13/// Live in-memory snapshot mirrored by the writable handle.
14#[derive(Debug, Clone)]
15pub struct SharedDbState {
16    pub catalog: Catalog,
17    pub latest: LatestMap,
18    pub indexes: IndexState,
19    pub segment_start: u64,
20    pub format_minor: u16,
21    /// Monotonic generation bumped on each mirror push (attached readers detect staleness).
22    pub generation: u64,
23}
24
25/// Registry entry: readers clone the inner [`Arc`] and release the lock immediately.
26pub type SharedDbHandle = Arc<RwLock<Arc<SharedDbState>>>;
27
28/// Canonical registry key so `./db.modelvault` and `/abs/db.modelvault` share one entry.
29pub fn registry_key(path: &Path) -> PathBuf {
30    path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
31}
32
33fn map() -> &'static Mutex<HashMap<PathBuf, SharedDbHandle>> {
34    static MAP: OnceLock<Mutex<HashMap<PathBuf, SharedDbHandle>>> = OnceLock::new();
35    MAP.get_or_init(|| Mutex::new(HashMap::new()))
36}
37
38pub fn register(path: &Path, state: SharedDbState) -> Result<SharedDbHandle, DbError> {
39    let key = registry_key(path);
40    let mut g = map()
41        .lock()
42        .map_err(|_| DbError::Io(std::io::Error::other("handle registry lock poisoned")))?;
43    if let Some(existing) = g.get(&key) {
44        let gen = existing
45            .read()
46            .map_err(|_| DbError::Io(std::io::Error::other("shared database lock poisoned")))?
47            .generation
48            .saturating_add(1);
49        let mut state = state;
50        state.generation = gen;
51        let mut w = existing
52            .write()
53            .map_err(|_| DbError::Io(std::io::Error::other("shared database lock poisoned")))?;
54        *w = Arc::new(state);
55        return Ok(Arc::clone(existing));
56    }
57    let mut state = state;
58    state.generation = 0;
59    let arc = Arc::new(RwLock::new(Arc::new(state)));
60    g.insert(key, Arc::clone(&arc));
61    Ok(arc)
62}
63
64pub fn get(path: &Path) -> Option<SharedDbHandle> {
65    let key = registry_key(path);
66    map().lock().ok().and_then(|g| g.get(&key).cloned())
67}
68
69#[allow(dead_code)]
70pub fn unregister(path: &Path) {
71    let key = registry_key(path);
72    if let Ok(mut g) = map().lock() {
73        g.remove(&key);
74    }
75}