faucet_cli/serve/
registry.rs1use dashmap::DashMap;
7use std::sync::atomic::{AtomicUsize, Ordering};
8use tokio::sync::Notify;
9use tokio_util::sync::CancellationToken;
10
11pub struct Registry {
12 tokens: DashMap<String, CancellationToken>,
13 queued: AtomicUsize,
14 in_flight: AtomicUsize,
15 max_queued: usize,
16 drained: Notify,
17}
18
19impl Registry {
20 pub fn new(max_queued: usize) -> Self {
21 Self {
22 tokens: DashMap::new(),
23 queued: AtomicUsize::new(0),
24 in_flight: AtomicUsize::new(0),
25 max_queued: max_queued.max(1),
26 drained: Notify::new(),
27 }
28 }
29
30 pub fn try_reserve(&self) -> bool {
34 self.queued
35 .try_update(Ordering::AcqRel, Ordering::Acquire, |cur| {
36 (cur < self.max_queued).then_some(cur + 1)
37 })
38 .is_ok()
39 }
40
41 pub fn release_reservation(&self) {
44 self.dec_queued();
45 }
46
47 pub fn register(&self, run_id: String, token: CancellationToken) {
48 self.tokens.insert(run_id, token);
49 }
50
51 pub fn mark_running(&self) {
53 self.dec_queued();
54 self.in_flight.fetch_add(1, Ordering::AcqRel);
55 }
56
57 pub fn mark_running_unqueued(&self) {
61 self.in_flight.fetch_add(1, Ordering::AcqRel);
62 }
63
64 pub fn mark_finished(&self, run_id: &str) {
66 self.dec_in_flight();
67 self.tokens.remove(run_id);
68 self.drained.notify_waiters();
69 }
70
71 pub fn mark_queued_cancelled(&self, run_id: &str) {
77 self.dec_queued();
78 self.tokens.remove(run_id);
79 self.drained.notify_waiters();
80 }
81
82 fn dec_queued(&self) {
85 let _ = self
86 .queued
87 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |q| {
88 Some(q.saturating_sub(1))
89 });
90 }
91
92 fn dec_in_flight(&self) {
94 let _ = self
95 .in_flight
96 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |n| {
97 Some(n.saturating_sub(1))
98 });
99 }
100
101 pub fn cancel(&self, run_id: &str) -> bool {
103 if let Some(t) = self.tokens.get(run_id) {
104 t.cancel();
105 true
106 } else {
107 false
108 }
109 }
110
111 pub fn queued(&self) -> usize {
112 self.queued.load(Ordering::Acquire)
113 }
114
115 pub fn in_flight(&self) -> usize {
116 self.in_flight.load(Ordering::Acquire)
117 }
118
119 pub fn is_full(&self) -> bool {
120 self.queued() >= self.max_queued
121 }
122
123 pub async fn wait_drained(&self) {
126 loop {
127 if self.queued() == 0 && self.in_flight() == 0 {
128 return;
129 }
130 let notified = self.drained.notified();
131 if self.queued() == 0 && self.in_flight() == 0 {
132 return;
133 }
134 notified.await;
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn reserve_respects_capacity() {
145 let r = Registry::new(2);
146 assert!(r.try_reserve());
147 assert!(r.try_reserve());
148 assert!(!r.try_reserve());
149 assert!(r.is_full());
150 r.release_reservation();
151 assert!(r.try_reserve());
152 }
153
154 #[test]
155 fn running_transition_moves_counters() {
156 let r = Registry::new(4);
157 r.try_reserve();
158 assert_eq!(r.queued(), 1);
159 r.mark_running();
160 assert_eq!(r.queued(), 0);
161 assert_eq!(r.in_flight(), 1);
162 r.mark_finished("x");
163 assert_eq!(r.in_flight(), 0);
164 }
165
166 #[test]
167 fn mark_running_unqueued_only_bumps_in_flight() {
168 let r = Registry::new(4);
169 r.mark_running_unqueued();
171 assert_eq!(
172 r.queued(),
173 0,
174 "queued must NOT be decremented (no slot was held)"
175 );
176 assert_eq!(r.in_flight(), 1);
177 r.mark_finished("x");
178 assert_eq!(r.in_flight(), 0);
179 assert_eq!(r.queued(), 0);
180 }
181
182 #[test]
183 fn queued_decrement_saturates_at_zero() {
184 let r = Registry::new(4);
185 r.mark_running(); assert_eq!(r.queued(), 0, "saturating: stays 0, never usize::MAX");
189 assert!(
190 r.try_reserve(),
191 "try_reserve still works (queued not wrapped)"
192 );
193 }
194
195 #[test]
196 fn cancel_reports_presence() {
197 let r = Registry::new(4);
198 let token = CancellationToken::new();
199 r.register("run1".into(), token.clone());
200 assert!(r.cancel("run1"));
201 assert!(token.is_cancelled());
202 assert!(!r.cancel("missing"));
203 }
204
205 #[test]
206 fn is_not_idle_while_queued() {
207 let r = Registry::new(4);
208 r.try_reserve();
209 assert_eq!(r.queued(), 1);
211 assert_eq!(r.in_flight(), 0);
212 }
213
214 #[tokio::test]
215 async fn wait_drained_returns_when_idle() {
216 let r = Registry::new(4);
217 r.wait_drained().await;
219 }
220}