refactor:moved key watch wake logic to specific module

This commit is contained in:
Tglman 2021-12-23 22:16:40 +00:00
parent 9b57c89df6
commit a889e884e6
5 changed files with 70 additions and 83 deletions

View file

@ -87,7 +87,7 @@ sha-1 = "0.9.8"
default = ["conduit_bin", "backend_sqlite"] default = ["conduit_bin", "backend_sqlite"]
backend_sled = ["sled"] backend_sled = ["sled"]
backend_sqlite = ["sqlite"] backend_sqlite = ["sqlite"]
backend_heed = ["heed", "crossbeam"] backend_heed = ["heed", "crossbeam", "parking_lot"]
sqlite = ["rusqlite", "parking_lot", "crossbeam", "tokio/signal"] sqlite = ["rusqlite", "parking_lot", "crossbeam", "tokio/signal"]
conduit_bin = [] # TODO: add rocket to this when it is optional conduit_bin = [] # TODO: add rocket to this when it is optional

View file

@ -12,6 +12,9 @@ pub mod sqlite;
#[cfg(feature = "heed")] #[cfg(feature = "heed")]
pub mod heed; pub mod heed;
#[cfg(any(feature = "sqlite", feature = "heed"))]
pub mod watchers;
pub trait DatabaseEngine: Sized { pub trait DatabaseEngine: Sized {
fn open(config: &Config) -> Result<Arc<Self>>; fn open(config: &Config) -> Result<Arc<Self>>;
fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>>; fn open_tree(self: &Arc<Self>, name: &'static str) -> Result<Arc<dyn Tree>>;

View file

@ -1,15 +1,13 @@
use super::super::Config; use super::{super::Config, watchers::Watchers};
use crossbeam::channel::{bounded, Sender as ChannelSender}; use crossbeam::channel::{bounded, Sender as ChannelSender};
use threadpool::ThreadPool; use threadpool::ThreadPool;
use crate::{Error, Result}; use crate::{Error, Result};
use std::{ use std::{
collections::HashMap,
future::Future, future::Future,
pin::Pin, pin::Pin,
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex},
}; };
use tokio::sync::oneshot::Sender;
use super::{DatabaseEngine, Tree}; use super::{DatabaseEngine, Tree};
@ -23,7 +21,7 @@ pub struct Engine {
pub struct EngineTree { pub struct EngineTree {
engine: Arc<Engine>, engine: Arc<Engine>,
tree: Arc<heed::UntypedDatabase>, tree: Arc<heed::UntypedDatabase>,
watchers: RwLock<HashMap<Vec<u8>, Vec<Sender<()>>>>, watchers: Watchers,
} }
fn convert_error(error: heed::Error) -> Error { fn convert_error(error: heed::Error) -> Error {
@ -60,7 +58,7 @@ impl DatabaseEngine for Engine {
.create_database(Some(name)) .create_database(Some(name))
.map_err(convert_error)?, .map_err(convert_error)?,
), ),
watchers: RwLock::new(HashMap::new()), watchers: Default::default(),
})) }))
} }
@ -145,29 +143,7 @@ impl Tree for EngineTree {
.put(&mut txn, &key, &value) .put(&mut txn, &key, &value)
.map_err(convert_error)?; .map_err(convert_error)?;
txn.commit().map_err(convert_error)?; txn.commit().map_err(convert_error)?;
self.watchers.wake(key);
let watchers = self.watchers.read().unwrap();
let mut triggered = Vec::new();
for length in 0..=key.len() {
if watchers.contains_key(&key[..length]) {
triggered.push(&key[..length]);
}
}
drop(watchers);
if !triggered.is_empty() {
let mut watchers = self.watchers.write().unwrap();
for prefix in triggered {
if let Some(txs) = watchers.remove(prefix) {
for tx in txs {
let _ = tx.send(());
}
}
}
};
Ok(()) Ok(())
} }
@ -223,18 +199,6 @@ impl Tree for EngineTree {
#[tracing::instrument(skip(self, prefix))] #[tracing::instrument(skip(self, prefix))]
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
let (tx, rx) = tokio::sync::oneshot::channel(); self.watchers.watch(prefix)
self.watchers
.write()
.unwrap()
.entry(prefix.to_vec())
.or_default()
.push(tx);
Box::pin(async move {
// Tx is never destroyed
rx.await.unwrap();
})
} }
} }

View file

@ -1,17 +1,15 @@
use super::{DatabaseEngine, Tree}; use super::{watchers::Watchers, DatabaseEngine, Tree};
use crate::{database::Config, Result}; use crate::{database::Config, Result};
use parking_lot::{Mutex, MutexGuard, RwLock}; use parking_lot::{Mutex, MutexGuard};
use rusqlite::{Connection, DatabaseName::Main, OptionalExtension}; use rusqlite::{Connection, DatabaseName::Main, OptionalExtension};
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::{hash_map, HashMap},
future::Future, future::Future,
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
}; };
use thread_local::ThreadLocal; use thread_local::ThreadLocal;
use tokio::sync::watch;
use tracing::debug; use tracing::debug;
thread_local! { thread_local! {
@ -113,7 +111,7 @@ impl DatabaseEngine for Engine {
Ok(Arc::new(SqliteTable { Ok(Arc::new(SqliteTable {
engine: Arc::clone(self), engine: Arc::clone(self),
name: name.to_owned(), name: name.to_owned(),
watchers: RwLock::new(HashMap::new()), watchers: Watchers::default(),
})) }))
} }
@ -126,7 +124,7 @@ impl DatabaseEngine for Engine {
pub struct SqliteTable { pub struct SqliteTable {
engine: Arc<Engine>, engine: Arc<Engine>,
name: String, name: String,
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>, watchers: Watchers,
} }
type TupleOfBytes = (Vec<u8>, Vec<u8>); type TupleOfBytes = (Vec<u8>, Vec<u8>);
@ -200,27 +198,7 @@ impl Tree for SqliteTable {
let guard = self.engine.write_lock(); let guard = self.engine.write_lock();
self.insert_with_guard(&guard, key, value)?; self.insert_with_guard(&guard, key, value)?;
drop(guard); drop(guard);
self.watchers.wake(key);
let watchers = self.watchers.read();
let mut triggered = Vec::new();
for length in 0..=key.len() {
if watchers.contains_key(&key[..length]) {
triggered.push(&key[..length]);
}
}
drop(watchers);
if !triggered.is_empty() {
let mut watchers = self.watchers.write();
for prefix in triggered {
if let Some(tx) = watchers.remove(prefix) {
let _ = tx.0.send(());
}
}
};
Ok(()) Ok(())
} }
@ -365,19 +343,7 @@ impl Tree for SqliteTable {
#[tracing::instrument(skip(self, prefix))] #[tracing::instrument(skip(self, prefix))]
fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> { fn watch_prefix<'a>(&'a self, prefix: &[u8]) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
let mut rx = match self.watchers.write().entry(prefix.to_vec()) { self.watchers.watch(prefix)
hash_map::Entry::Occupied(o) => o.get().1.clone(),
hash_map::Entry::Vacant(v) => {
let (tx, rx) = tokio::sync::watch::channel(());
v.insert((tx, rx.clone()));
rx
}
};
Box::pin(async move {
// Tx is never destroyed
rx.changed().await.unwrap();
})
} }
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]

View file

@ -0,0 +1,54 @@
use parking_lot::RwLock;
use std::{
collections::{hash_map, HashMap},
future::Future,
pin::Pin,
};
use tokio::sync::watch;
#[derive(Default)]
pub(super) struct Watchers {
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>,
}
impl Watchers {
pub(super) fn watch<'a>(
&'a self,
prefix: &[u8],
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
let mut rx = match self.watchers.write().entry(prefix.to_vec()) {
hash_map::Entry::Occupied(o) => o.get().1.clone(),
hash_map::Entry::Vacant(v) => {
let (tx, rx) = tokio::sync::watch::channel(());
v.insert((tx, rx.clone()));
rx
}
};
Box::pin(async move {
// Tx is never destroyed
rx.changed().await.unwrap();
})
}
pub(super) fn wake(&self, key: &[u8]) {
let watchers = self.watchers.read();
let mut triggered = Vec::new();
for length in 0..=key.len() {
if watchers.contains_key(&key[..length]) {
triggered.push(&key[..length]);
}
}
drop(watchers);
if !triggered.is_empty() {
let mut watchers = self.watchers.write();
for prefix in triggered {
if let Some(tx) = watchers.remove(prefix) {
let _ = tx.0.send(());
}
}
};
}
}