reifydb_engine/queue/
wake.rs1use std::{
5 collections::{HashMap, VecDeque},
6 fmt,
7 sync::Arc,
8};
9
10use reifydb_core::interface::catalog::id::QueueId;
11use reifydb_runtime::sync::{mutex::Mutex, waiter::WaiterHandle};
12
13#[derive(Clone)]
14pub struct QueueWakeRegistry(Arc<QueueWakeRegistryInner>);
15
16struct QueueWakeRegistryInner {
17 queues: Mutex<HashMap<QueueId, VecDeque<Arc<WaiterHandle>>>>,
18}
19
20impl fmt::Debug for QueueWakeRegistry {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.debug_struct("QueueWakeRegistry").finish_non_exhaustive()
23 }
24}
25
26impl Default for QueueWakeRegistry {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl QueueWakeRegistry {
33 pub fn new() -> Self {
34 Self(Arc::new(QueueWakeRegistryInner {
35 queues: Mutex::new(HashMap::new()),
36 }))
37 }
38
39 pub fn register(&self, queue: QueueId, waiter: Arc<WaiterHandle>) {
40 self.0.queues.lock().entry(queue).or_default().push_back(waiter);
41 }
42
43 pub fn deregister(&self, queue: QueueId, waiter: &Arc<WaiterHandle>) {
44 let mut queues = self.0.queues.lock();
45 let Some(waiters) = queues.get_mut(&queue) else {
46 return;
47 };
48 if let Some(index) = waiters.iter().position(|parked| Arc::ptr_eq(parked, waiter)) {
49 waiters.remove(index);
50 }
51 if waiters.is_empty() {
52 queues.remove(&queue);
53 }
54 }
55
56 pub fn nudge(&self, queue: QueueId, count: usize) {
57 if count == 0 {
58 return;
59 }
60
61 let woken = {
62 let mut queues = self.0.queues.lock();
63 let Some(waiters) = queues.get_mut(&queue) else {
64 return;
65 };
66 let taken: Vec<Arc<WaiterHandle>> = waiters.drain(..count.min(waiters.len())).collect();
67 if waiters.is_empty() {
68 queues.remove(&queue);
69 }
70 taken
71 };
72
73 for waiter in woken {
74 waiter.notify();
75 }
76 }
77
78 pub fn parked(&self, queue: QueueId) -> usize {
79 self.0.queues.lock().get(&queue).map_or(0, VecDeque::len)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use reifydb_value::value::duration::Duration;
86
87 use super::*;
88
89 fn zero() -> Duration {
90 Duration::from_milliseconds(0).unwrap()
91 }
92
93 fn park(registry: &QueueWakeRegistry, queue: QueueId) -> Arc<WaiterHandle> {
94 let waiter = Arc::new(WaiterHandle::new());
95 registry.register(queue, waiter.clone());
96 waiter
97 }
98
99 #[test]
100 fn test_a_nudge_wakes_the_oldest_parked_worker_first() {
101 let registry = QueueWakeRegistry::new();
106 let queue = QueueId(7);
107
108 let first = park(®istry, queue);
109 let second = park(®istry, queue);
110 let third = park(®istry, queue);
111
112 registry.nudge(queue, 1);
113
114 assert!(first.wait_timeout(zero()), "the oldest parked worker must be the one woken");
115 assert!(!second.wait_timeout(zero()), "a single item must not wake a second worker");
116 assert!(!third.wait_timeout(zero()));
117 assert_eq!(registry.parked(queue), 2, "the woken waiter must leave the park list");
118 }
119
120 #[test]
121 fn test_a_nudge_that_arrives_before_the_wait_is_not_lost() {
122 let registry = QueueWakeRegistry::new();
127 let queue = QueueId(1);
128
129 let waiter = park(®istry, queue);
130 registry.nudge(queue, 1);
131
132 assert!(waiter.wait_timeout(zero()), "a nudge delivered before the wait must still release it");
133 }
134
135 #[test]
136 fn test_deregistering_one_waiter_leaves_the_others_parked() {
137 let registry = QueueWakeRegistry::new();
141 let queue = QueueId(3);
142
143 let first = park(®istry, queue);
144 let second = park(®istry, queue);
145
146 registry.deregister(queue, &second);
147 assert_eq!(registry.parked(queue), 1);
148
149 registry.nudge(queue, 1);
150
151 assert!(first.wait_timeout(zero()), "the surviving waiter must still be reachable");
152 assert!(!second.wait_timeout(zero()), "a deregistered waiter must never be notified");
153 assert_eq!(registry.parked(queue), 0);
154 }
155
156 #[test]
157 fn test_nudging_more_items_than_parked_workers_is_harmless() {
158 let registry = QueueWakeRegistry::new();
162 let queue = QueueId(9);
163
164 let first = park(®istry, queue);
165 let second = park(®istry, queue);
166
167 registry.nudge(queue, 5);
168
169 assert!(first.wait_timeout(zero()));
170 assert!(second.wait_timeout(zero()));
171 assert_eq!(registry.parked(queue), 0);
172 }
173
174 #[test]
175 fn test_queues_do_not_share_a_park_list() {
176 let registry = QueueWakeRegistry::new();
180
181 let mine = park(®istry, QueueId(1));
182 let theirs = park(®istry, QueueId(2));
183
184 registry.nudge(QueueId(1), 4);
185
186 assert!(mine.wait_timeout(zero()));
187 assert!(!theirs.wait_timeout(zero()), "a nudge must not cross queue boundaries");
188 assert_eq!(registry.parked(QueueId(2)), 1);
189 }
190}