use std::{
collections::{
btree_map::Entry,
BTreeMap,
},
os::raw::c_uint,
path::{
Path,
PathBuf,
},
result,
sync::{
Arc,
RwLock,
},
};
use lazy_static::lazy_static;
use crate::{
backend::{
BackendEnvironment,
BackendEnvironmentBuilder,
LmdbEnvironment,
SafeModeEnvironment,
},
error::StoreError,
helpers::canonicalize_path,
Rkv,
};
type Result<T> = result::Result<T, StoreError>;
type SharedRkv<E> = Arc<RwLock<Rkv<E>>>;
lazy_static! {
static ref MANAGER_LMDB: RwLock<Manager<LmdbEnvironment>> = RwLock::new(Manager::new());
static ref MANAGER_SAFE_MODE: RwLock<Manager<SafeModeEnvironment>> = RwLock::new(Manager::new());
}
pub struct Manager<E> {
environments: BTreeMap<PathBuf, SharedRkv<E>>,
}
impl<'e, E> Manager<E>
where
E: BackendEnvironment<'e>,
{
fn new() -> Manager<E> {
Manager {
environments: Default::default(),
}
}
pub fn get<'p, P>(&self, path: P) -> Result<Option<SharedRkv<E>>>
where
P: Into<&'p Path>,
{
let canonical = canonicalize_path(path)?;
Ok(self.environments.get(&canonical).cloned())
}
pub fn get_or_create<'p, F, P>(&mut self, path: P, f: F) -> Result<SharedRkv<E>>
where
F: FnOnce(&Path) -> Result<Rkv<E>>,
P: Into<&'p Path>,
{
let canonical = canonicalize_path(path)?;
Ok(match self.environments.entry(canonical) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let k = Arc::new(RwLock::new(f(e.key().as_path())?));
e.insert(k).clone()
},
})
}
pub fn get_or_create_with_capacity<'p, F, P>(&mut self, path: P, capacity: c_uint, f: F) -> Result<SharedRkv<E>>
where
F: FnOnce(&Path, c_uint) -> Result<Rkv<E>>,
P: Into<&'p Path>,
{
let canonical = canonicalize_path(path)?;
Ok(match self.environments.entry(canonical) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let k = Arc::new(RwLock::new(f(e.key().as_path(), capacity)?));
e.insert(k).clone()
},
})
}
pub fn get_or_create_from_builder<'p, F, P, B>(&mut self, path: P, builder: B, f: F) -> Result<SharedRkv<E>>
where
F: FnOnce(&Path, B) -> Result<Rkv<E>>,
P: Into<&'p Path>,
B: BackendEnvironmentBuilder<'e, Environment = E>,
{
let canonical = canonicalize_path(path)?;
Ok(match self.environments.entry(canonical) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let k = Arc::new(RwLock::new(f(e.key().as_path(), builder)?));
e.insert(k).clone()
},
})
}
pub fn try_close_and_delete<'p, P>(&mut self, path: P) -> Result<()>
where
P: Into<&'p Path>,
{
let canonical = canonicalize_path(path)?;
match self.environments.entry(canonical) {
Entry::Vacant(_) => {}, Entry::Occupied(e) => {
if Arc::strong_count(e.get()) == 1 {
if let Ok(env) = Arc::try_unwrap(e.remove()) {
env.into_inner()?.close_and_delete()?;
}
}
},
}
Ok(())
}
}
impl Manager<LmdbEnvironment> {
pub fn singleton() -> &'static RwLock<Manager<LmdbEnvironment>> {
&*MANAGER_LMDB
}
}
impl Manager<SafeModeEnvironment> {
pub fn singleton() -> &'static RwLock<Manager<SafeModeEnvironment>> {
&*MANAGER_SAFE_MODE
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
use std::fs;
use tempfile::Builder;
use backend::Lmdb;
#[test]
fn test_mutate_managed_rkv() {
let mut manager = Manager::<LmdbEnvironment>::new();
let root1 = Builder::new().prefix("test_mutate_managed_rkv_1").tempdir().expect("tempdir");
fs::create_dir_all(root1.path()).expect("dir created");
let path1 = root1.path();
let arc = manager.get_or_create(path1, Rkv::new::<Lmdb>).expect("created");
let root2 = Builder::new().prefix("test_mutate_managed_rkv_2").tempdir().expect("tempdir");
fs::create_dir_all(root2.path()).expect("dir created");
let path2 = root2.path();
{
let mut rkv = arc.write().expect("guard");
let rkv2 = Rkv::new::<Lmdb>(path2).expect("Rkv");
*rkv = rkv2;
}
let path1_arc = manager.get(path1).expect("success").expect("existed");
assert!(Arc::ptr_eq(&path1_arc, &arc));
let path2_arc = manager.get_or_create(path2, Rkv::new::<Lmdb>).expect("success");
assert!(!Arc::ptr_eq(&path2_arc, &arc));
}
}