Skip to main content

tuwunel_database/map/
watch.rs

1use std::{
2	collections::{BTreeMap, btree_map::Entry},
3	ops::RangeToInclusive,
4	sync::Mutex,
5};
6
7use futures::pin_mut;
8use serde::Serialize;
9use tokio::sync::watch::{Receiver, Sender, channel};
10use tuwunel_core::{debug, defer, implement, smallvec::SmallVec};
11
12use crate::keyval::{KeyBuf, serialize_key};
13
14/// Stores prefix subscriptions in raw-key order.
15///
16/// Ordered storage lets notification walk reverse prefix candidates for a
17/// changed key. The first nonmatching candidate terminates that walk.
18type Watchers = Mutex<BTreeMap<KeyBuf, Sender<()>>>;
19/// Buffers stale watcher keys discovered during notification.
20///
21/// The one-entry inline budget avoids allocation when notification reaps no
22/// more than one closed subscription. Larger reap batches spill to the heap.
23type KeyVec = SmallVec<[KeyBuf; 1]>;
24
25/// Owns the prefix subscriptions registered for a map.
26///
27/// A mutex protects subscription insertion, notification, and stale-entry
28/// removal.
29#[derive(Default)]
30pub(super) struct Watch {
31	watchers: Watchers,
32}
33
34/// Waits for the next map mutation under a serialized prefix.
35///
36/// The prefix is encoded once before subscription. The stored subscription is
37/// reaped after a later matching notification observes that its receiver has
38/// closed.
39///
40/// # Panics
41///
42/// Panics if prefix serialization fails, the watcher mutex is poisoned, or the
43/// sender disappears before notification.
44#[implement(super::Map)]
45pub fn watch_prefix<K>(&self, prefix: K) -> impl Future<Output = ()> + Send + '_
46where
47	K: Serialize,
48{
49	let prefix = serialize_key(prefix).expect("failed to serialize watch prefix key");
50	self.watch_raw_prefix(&prefix)
51}
52
53/// Waits once for a map mutation under a raw prefix.
54///
55/// The prefix is copied into the subscription table. A drop guard removes the
56/// entry immediately when this future owns its last receiver.
57///
58/// # Panics
59///
60/// Panics if the watcher mutex is poisoned or the sender disappears before
61/// notification.
62#[implement(super::Map)]
63pub fn watch_raw_prefix_once<K>(&self, prefix: K) -> impl Future<Output = ()> + Send + '_
64where
65	K: AsRef<[u8]>,
66{
67	let key: KeyBuf = prefix.as_ref().into();
68	let rx = self.subscribe(key.clone());
69
70	async move {
71		pin_mut!(rx);
72
73		// We are still subscribed, so a receiver count of one means we are the last.
74		defer! {{
75			let mut watchers = self.watch.watchers.lock().expect("locked");
76			if watchers.get(&key).is_some_and(|tx| tx.receiver_count() == 1) {
77				watchers.remove(&key);
78			}
79		}}
80
81		rx.changed()
82			.await
83			.expect("watcher sender dropped");
84	}
85}
86
87/// Waits for the next map mutation under a borrowed raw prefix.
88///
89/// The prefix is copied into the subscription table. The stored subscription is
90/// reaped after a later matching notification observes that its receiver has
91/// closed.
92///
93/// # Panics
94///
95/// Panics if the watcher mutex is poisoned or the sender disappears before
96/// notification.
97#[implement(super::Map)]
98pub fn watch_raw_prefix<'a, K>(&self, prefix: &'a K) -> impl Future<Output = ()> + Send + use<K>
99where
100	K: AsRef<[u8]> + ?Sized + 'a,
101{
102	let rx = self.subscribe(prefix.as_ref().into());
103
104	async move {
105		pin_mut!(rx);
106		rx.changed()
107			.await
108			.expect("watcher sender dropped");
109	}
110}
111
112/// Subscribes to mutations under an owned raw prefix.
113///
114/// Existing prefixes share one watch sender, while new prefixes create a fresh
115/// channel.
116///
117/// # Panics
118///
119/// Panics if the watcher mutex is poisoned.
120#[implement(super::Map)]
121fn subscribe(&self, key: KeyBuf) -> Receiver<()> {
122	match self
123		.watch
124		.watchers
125		.lock()
126		.expect("locked")
127		.entry(key)
128	{
129		| Entry::Occupied(node) => node.get().subscribe(),
130		| Entry::Vacant(node) => {
131			let (tx, rx) = channel(());
132			node.insert(tx);
133			rx
134		},
135	}
136}
137
138/// Notifies subscriptions whose prefixes match a mutated raw key.
139///
140/// Closed subscriptions discovered during the ordered prefix walk are removed
141/// in the same critical section. Live subscriptions remain available for later
142/// mutations.
143///
144/// # Panics
145///
146/// Panics if the watcher mutex is poisoned.
147#[implement(super::Map)]
148#[tracing::instrument(
149	level = "trace",
150	skip_all,
151	fields(
152		map = self.name(),
153		key = str::from_utf8(key.as_ref()).unwrap_or("<binary>"),
154	)
155)]
156pub(crate) fn notify<K>(&self, key: &K)
157where
158	K: AsRef<[u8]> + Ord + ?Sized,
159{
160	let range = RangeToInclusive::<KeyBuf> { end: key.as_ref().into() };
161
162	let mut watchers = self.watch.watchers.lock().expect("locked");
163
164	let num_notified = watchers
165		.range(range)
166		.rev()
167		.take_while(|(k, _)| key.as_ref().starts_with(k))
168		.filter_map(|(k, tx)| tx.send(()).is_err().then_some(k))
169		.cloned()
170		.collect::<KeyVec>()
171		.into_iter()
172		.fold(0_usize, |num_notified, key| {
173			watchers.remove(&key);
174			num_notified.saturating_add(1)
175		});
176
177	if num_notified > 0 {
178		debug!(watchers = watchers.len(), num_notified, "notified");
179	}
180}
181
182#[cfg(test)]
183mod tests {
184	use tokio::sync::watch::channel;
185
186	// Pins the tokio contract the reaper relies on: receiver_count() reflects a
187	// just-dropped Receiver and send() fails once no receiver remains.
188	#[test]
189	fn receiver_count_reaps_at_last_drop() {
190		let (tx, rx) = channel(());
191		assert_eq!(tx.receiver_count(), 1, "fresh channel has one receiver");
192
193		let rx2 = tx.subscribe();
194		assert_eq!(tx.receiver_count(), 2, "subscribe adds a receiver");
195
196		drop(rx2);
197		assert_eq!(tx.receiver_count(), 1, "drop is reflected synchronously");
198
199		drop(rx);
200		assert_eq!(tx.receiver_count(), 0, "last drop leaves no receiver");
201		assert!(tx.send(()).is_err(), "send fails with zero receivers");
202	}
203}