Refactor legacy database watcher system.
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
@@ -24,14 +24,12 @@ mod rev_stream_prefix;
|
|||||||
mod stream;
|
mod stream;
|
||||||
mod stream_from;
|
mod stream_from;
|
||||||
mod stream_prefix;
|
mod stream_prefix;
|
||||||
|
mod watch;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::AsRef,
|
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
fmt,
|
fmt,
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,12 +40,13 @@ pub(crate) use self::options::{
|
|||||||
cache_iter_options_default, cache_read_options_default, iter_options_default,
|
cache_iter_options_default, cache_read_options_default, iter_options_default,
|
||||||
read_options_default, write_options_default,
|
read_options_default, write_options_default,
|
||||||
};
|
};
|
||||||
|
use self::watch::Watch;
|
||||||
pub use self::{get_batch::Get, qry_batch::Qry};
|
pub use self::{get_batch::Get, qry_batch::Qry};
|
||||||
use crate::{Engine, watchers::Watchers};
|
use crate::Engine;
|
||||||
|
|
||||||
pub struct Map {
|
pub struct Map {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
watchers: Watchers,
|
watch: Watch,
|
||||||
cf: Arc<ColumnFamily>,
|
cf: Arc<ColumnFamily>,
|
||||||
db: Arc<Engine>,
|
db: Arc<Engine>,
|
||||||
read_options: ReadOptions,
|
read_options: ReadOptions,
|
||||||
@@ -59,7 +58,7 @@ impl Map {
|
|||||||
pub(crate) fn open(db: &Arc<Engine>, name: &'static str) -> Result<Arc<Self>> {
|
pub(crate) fn open(db: &Arc<Engine>, name: &'static str) -> Result<Arc<Self>> {
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
name,
|
name,
|
||||||
watchers: Watchers::default(),
|
watch: Watch::default(),
|
||||||
cf: open::open(db, name),
|
cf: open::open(db, name),
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
read_options: read_options_default(db),
|
read_options: read_options_default(db),
|
||||||
@@ -68,17 +67,6 @@ impl Map {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn watch_prefix<'a, K>(
|
|
||||||
&'a self,
|
|
||||||
prefix: &K,
|
|
||||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
|
|
||||||
where
|
|
||||||
K: AsRef<[u8]> + ?Sized + Debug,
|
|
||||||
{
|
|
||||||
self.watchers.watch(prefix.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn property_integer(&self, name: &CStr) -> Result<u64> {
|
pub fn property_integer(&self, name: &CStr) -> Result<u64> {
|
||||||
self.db.property_integer(&self.cf(), name)
|
self.db.property_integer(&self.cf(), name)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ where
|
|||||||
self.db.flush().expect("database flush error");
|
self.db.flush().expect("database flush error");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.watchers.wake(key.as_ref());
|
self.notify(key.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Map)]
|
#[implement(super::Map)]
|
||||||
|
|||||||
69
src/database/map/watch.rs
Normal file
69
src/database/map/watch.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, btree_map::Entry},
|
||||||
|
future::Future,
|
||||||
|
ops::RangeToInclusive,
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::pin_mut;
|
||||||
|
use tokio::sync::watch::{Sender, channel};
|
||||||
|
use tuwunel_core::implement;
|
||||||
|
|
||||||
|
use crate::keyval::KeyBuf;
|
||||||
|
|
||||||
|
type Watchers = Mutex<BTreeMap<KeyBuf, Sender<()>>>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct Watch {
|
||||||
|
watchers: Watchers,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[implement(super::Map)]
|
||||||
|
pub fn watch_raw_prefix<K>(&self, prefix: &K) -> impl Future<Output = ()> + Send + use<K>
|
||||||
|
where
|
||||||
|
K: AsRef<[u8]> + ?Sized,
|
||||||
|
{
|
||||||
|
let rx = match self
|
||||||
|
.watch
|
||||||
|
.watchers
|
||||||
|
.lock()
|
||||||
|
.expect("locked")
|
||||||
|
.entry(prefix.as_ref().into())
|
||||||
|
{
|
||||||
|
| Entry::Occupied(node) => node.get().subscribe(),
|
||||||
|
| Entry::Vacant(node) => {
|
||||||
|
let (tx, rx) = channel(());
|
||||||
|
node.insert(tx);
|
||||||
|
rx
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async move {
|
||||||
|
pin_mut!(rx);
|
||||||
|
rx.changed()
|
||||||
|
.await
|
||||||
|
.expect("watcher sender dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[implement(super::Map)]
|
||||||
|
pub(crate) fn notify<K>(&self, key: &K)
|
||||||
|
where
|
||||||
|
K: AsRef<[u8]> + Ord + ?Sized,
|
||||||
|
{
|
||||||
|
let range = RangeToInclusive::<KeyBuf> { end: key.as_ref().into() };
|
||||||
|
|
||||||
|
let mut watchers = self.watch.watchers.lock().expect("locked");
|
||||||
|
|
||||||
|
watchers
|
||||||
|
.range(range)
|
||||||
|
.rev()
|
||||||
|
.take_while(|(k, _)| key.as_ref().starts_with(k))
|
||||||
|
.filter_map(|(k, tx)| tx.send(()).is_err().then_some(k))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|k| {
|
||||||
|
watchers.remove(&k);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -22,7 +22,6 @@ mod stream;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
pub(crate) mod util;
|
pub(crate) mod util;
|
||||||
mod watchers;
|
|
||||||
|
|
||||||
use std::{ops::Index, sync::Arc};
|
use std::{ops::Index, sync::Arc};
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::{HashMap, hash_map},
|
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::RwLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
use tokio::sync::watch;
|
|
||||||
|
|
||||||
type Watcher = RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct Watchers {
|
|
||||||
watchers: Watcher,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Watchers {
|
|
||||||
pub(crate) fn watch<'a>(
|
|
||||||
&'a self,
|
|
||||||
prefix: &[u8],
|
|
||||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
|
||||||
let mut rx = match self
|
|
||||||
.watchers
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.entry(prefix.to_vec())
|
|
||||||
{
|
|
||||||
| hash_map::Entry::Occupied(o) => o.get().1.clone(),
|
|
||||||
| hash_map::Entry::Vacant(v) => {
|
|
||||||
let (tx, rx) = watch::channel(());
|
|
||||||
v.insert((tx, rx.clone()));
|
|
||||||
rx
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
// Tx is never destroyed
|
|
||||||
rx.changed().await.unwrap();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn wake(&self, key: &[u8]) {
|
|
||||||
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(tx) = watchers.remove(prefix) {
|
|
||||||
tx.0.send(())
|
|
||||||
.expect("channel should still be open");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,33 +20,39 @@ pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
|
|||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.todeviceid_events
|
.todeviceid_events
|
||||||
.watch_prefix(&userdeviceid_prefix),
|
.watch_raw_prefix(&userdeviceid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.userroomid_joined
|
.userroomid_joined
|
||||||
.watch_prefix(&userid_prefix),
|
.watch_raw_prefix(&userid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.userroomid_invitestate
|
.userroomid_invitestate
|
||||||
.watch_prefix(&userid_prefix),
|
.watch_raw_prefix(&userid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.userroomid_leftstate
|
.userroomid_leftstate
|
||||||
.watch_prefix(&userid_prefix),
|
.watch_raw_prefix(&userid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.userroomid_notificationcount
|
.userroomid_notificationcount
|
||||||
.watch_prefix(&userid_prefix),
|
.watch_raw_prefix(&userid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.userroomid_highlightcount
|
.userroomid_highlightcount
|
||||||
.watch_prefix(&userid_prefix),
|
.watch_raw_prefix(&userid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Events for rooms we are in
|
// Events for rooms we are in
|
||||||
@@ -66,7 +72,8 @@ pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
|
|||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.keychangeid_userid
|
.keychangeid_userid
|
||||||
.watch_prefix(&roomid_prefix),
|
.watch_raw_prefix(&roomid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Room account data
|
// Room account data
|
||||||
@@ -76,12 +83,18 @@ pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
|
|||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.roomusertype_roomuserdataid
|
.roomusertype_roomuserdataid
|
||||||
.watch_prefix(&roomuser_prefix),
|
.watch_raw_prefix(&roomuser_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// PDUs
|
// PDUs
|
||||||
let short_roomid = short_roomid.to_be_bytes().to_vec();
|
let short_roomid = short_roomid.to_be_bytes().to_vec();
|
||||||
futures.push(self.db.pduid_pdu.watch_prefix(&short_roomid));
|
futures.push(
|
||||||
|
self.db
|
||||||
|
.pduid_pdu
|
||||||
|
.watch_raw_prefix(&short_roomid)
|
||||||
|
.boxed(),
|
||||||
|
);
|
||||||
|
|
||||||
// EDUs
|
// EDUs
|
||||||
let typing_room_id = room_id.to_owned();
|
let typing_room_id = room_id.to_owned();
|
||||||
@@ -96,7 +109,8 @@ pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
|
|||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.readreceiptid_readreceipt
|
.readreceiptid_readreceipt
|
||||||
.watch_prefix(&roomid_prefix),
|
.watch_raw_prefix(&roomid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,21 +120,24 @@ pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result {
|
|||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.roomusertype_roomuserdataid
|
.roomusertype_roomuserdataid
|
||||||
.watch_prefix(&globaluserdata_prefix),
|
.watch_raw_prefix(&globaluserdata_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// More key changes (used when user is not joined to any rooms)
|
// More key changes (used when user is not joined to any rooms)
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.keychangeid_userid
|
.keychangeid_userid
|
||||||
.watch_prefix(&userid_prefix),
|
.watch_raw_prefix(&userid_prefix)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// One time keys
|
// One time keys
|
||||||
futures.push(
|
futures.push(
|
||||||
self.db
|
self.db
|
||||||
.userid_lastonetimekeyupdate
|
.userid_lastonetimekeyupdate
|
||||||
.watch_prefix(&userid_bytes),
|
.watch_raw_prefix(&userid_bytes)
|
||||||
|
.boxed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Server shutdown
|
// Server shutdown
|
||||||
|
|||||||
Reference in New Issue
Block a user