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 nudge_all(&self, queue: QueueId) {
79 let woken = self.0.queues.lock().remove(&queue).unwrap_or_default();
80 for waiter in woken {
81 waiter.notify();
82 }
83 }
84
85 pub fn parked(&self, queue: QueueId) -> usize {
86 self.0.queues.lock().get(&queue).map_or(0, VecDeque::len)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use reifydb_value::value::duration::Duration;
93
94 use super::*;
95
96 fn zero() -> Duration {
97 Duration::from_milliseconds(0).unwrap()
98 }
99
100 fn park(registry: &QueueWakeRegistry, queue: QueueId) -> Arc<WaiterHandle> {
101 let waiter = Arc::new(WaiterHandle::new());
102 registry.register(queue, waiter.clone());
103 waiter
104 }
105
106 #[test]
107 fn test_a_nudge_wakes_the_oldest_parked_worker_first() {
108 let registry = QueueWakeRegistry::new();
113 let queue = QueueId(7);
114
115 let first = park(®istry, queue);
116 let second = park(®istry, queue);
117 let third = park(®istry, queue);
118
119 registry.nudge(queue, 1);
120
121 assert!(first.wait_timeout(zero()), "the oldest parked worker must be the one woken");
122 assert!(!second.wait_timeout(zero()), "a single item must not wake a second worker");
123 assert!(!third.wait_timeout(zero()));
124 assert_eq!(registry.parked(queue), 2, "the woken waiter must leave the park list");
125 }
126
127 #[test]
128 fn test_a_nudge_that_arrives_before_the_wait_is_not_lost() {
129 let registry = QueueWakeRegistry::new();
134 let queue = QueueId(1);
135
136 let waiter = park(®istry, queue);
137 registry.nudge(queue, 1);
138
139 assert!(waiter.wait_timeout(zero()), "a nudge delivered before the wait must still release it");
140 }
141
142 #[test]
143 fn test_deregistering_one_waiter_leaves_the_others_parked() {
144 let registry = QueueWakeRegistry::new();
148 let queue = QueueId(3);
149
150 let first = park(®istry, queue);
151 let second = park(®istry, queue);
152
153 registry.deregister(queue, &second);
154 assert_eq!(registry.parked(queue), 1);
155
156 registry.nudge(queue, 1);
157
158 assert!(first.wait_timeout(zero()), "the surviving waiter must still be reachable");
159 assert!(!second.wait_timeout(zero()), "a deregistered waiter must never be notified");
160 assert_eq!(registry.parked(queue), 0);
161 }
162
163 #[test]
164 fn test_nudging_more_items_than_parked_workers_is_harmless() {
165 let registry = QueueWakeRegistry::new();
169 let queue = QueueId(9);
170
171 let first = park(®istry, queue);
172 let second = park(®istry, queue);
173
174 registry.nudge(queue, 5);
175
176 assert!(first.wait_timeout(zero()));
177 assert!(second.wait_timeout(zero()));
178 assert_eq!(registry.parked(queue), 0);
179 }
180
181 #[test]
182 fn test_queues_do_not_share_a_park_list() {
183 let registry = QueueWakeRegistry::new();
187
188 let mine = park(®istry, QueueId(1));
189 let theirs = park(®istry, QueueId(2));
190
191 registry.nudge(QueueId(1), 4);
192
193 assert!(mine.wait_timeout(zero()));
194 assert!(!theirs.wait_timeout(zero()), "a nudge must not cross queue boundaries");
195 assert_eq!(registry.parked(QueueId(2)), 1);
196 }
197
198 #[test]
199 fn test_nudging_an_unknown_queue_does_nothing() {
200 let registry = QueueWakeRegistry::new();
201
202 registry.nudge(QueueId(42), 3);
203 registry.nudge_all(QueueId(42));
204
205 assert_eq!(registry.parked(QueueId(42)), 0);
206 }
207}