1use std::collections::VecDeque;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::Arc;
18
19use parking_lot::RwLock;
20
21use atomr_core::actor::UntypedActorRef;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct FenceToken(pub u64);
28
29pub struct HandoffState(pub Vec<u8>);
36
37#[async_trait::async_trait]
45pub trait SingletonHandoff: Send + 'static {
46 async fn prepare_handoff(&mut self) -> HandoffState;
47 async fn assume(&mut self, prior: Option<HandoffState>, fence: FenceToken);
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52#[non_exhaustive]
53pub enum SingletonState {
54 Inactive,
56 Starting,
58 Active { ref_: UntypedActorRef, here: bool },
60 HandingOver,
62}
63
64type BufferedMsg = Box<dyn FnOnce(&UntypedActorRef) + Send + 'static>;
69
70pub struct ClusterSingletonManager {
74 state: RwLock<SingletonState>,
75 buffer: parking_lot::Mutex<VecDeque<BufferedMsg>>,
76 buffer_size: usize,
77 drops: parking_lot::Mutex<u64>,
79 fence: AtomicU64,
81 handoff: tokio::sync::Mutex<Option<Box<dyn SingletonHandoff>>>,
83 prior: parking_lot::Mutex<Option<HandoffState>>,
87}
88
89impl Default for ClusterSingletonManager {
90 fn default() -> Self {
91 Self {
92 state: RwLock::new(SingletonState::Inactive),
93 buffer: parking_lot::Mutex::new(VecDeque::new()),
94 buffer_size: 1_000,
95 drops: parking_lot::Mutex::new(0),
96 fence: AtomicU64::new(0),
97 handoff: tokio::sync::Mutex::new(None),
98 prior: parking_lot::Mutex::new(None),
99 }
100 }
101}
102
103impl ClusterSingletonManager {
104 pub fn new() -> Arc<Self> {
105 Arc::new(Self::default())
106 }
107
108 pub fn with_buffer_size(size: usize) -> Arc<Self> {
110 Arc::new(Self { buffer_size: size, ..Self::default() })
111 }
112
113 pub fn with_handoff(handoff: Box<dyn SingletonHandoff>) -> Arc<Self> {
115 Arc::new(Self { handoff: tokio::sync::Mutex::new(Some(handoff)), ..Self::default() })
116 }
117
118 pub fn state(&self) -> SingletonState {
119 self.state.read().clone()
120 }
121
122 pub fn fence(&self) -> FenceToken {
125 FenceToken(self.fence.load(Ordering::SeqCst))
126 }
127
128 pub fn set_active_here(&self, r: UntypedActorRef) {
134 let token = FenceToken(self.fence.fetch_add(1, Ordering::SeqCst) + 1);
136 self.run_assume(token);
137 *self.state.write() = SingletonState::Active { ref_: r.clone(), here: true };
138 self.flush(&r);
139 }
140
141 fn run_assume(&self, token: FenceToken) {
144 if let Ok(mut guard) = self.handoff.try_lock() {
147 if let Some(h) = guard.as_mut() {
148 let prior = self.prior.lock().take();
149 futures::executor::block_on(h.assume(prior, token));
150 }
151 }
152 }
153
154 pub fn set_active_remote(&self, r: UntypedActorRef) {
156 *self.state.write() = SingletonState::Active { ref_: r.clone(), here: false };
157 self.flush(&r);
158 }
159
160 pub fn begin_handover(&self) {
166 if let Ok(mut guard) = self.handoff.try_lock() {
170 if let Some(h) = guard.as_mut() {
171 let captured = futures::executor::block_on(h.prepare_handoff());
172 *self.prior.lock() = Some(captured);
173 }
174 }
175 *self.state.write() = SingletonState::HandingOver;
176 }
177
178 pub fn begin_starting(&self) {
180 *self.state.write() = SingletonState::Starting;
181 }
182
183 pub fn clear(&self) {
185 *self.state.write() = SingletonState::Inactive;
186 }
187
188 pub fn current(&self) -> Option<UntypedActorRef> {
189 match &*self.state.read() {
190 SingletonState::Active { ref_, .. } => Some(ref_.clone()),
191 _ => None,
192 }
193 }
194
195 fn buffer_or_deliver<F>(&self, deliver: F) -> bool
200 where
201 F: FnOnce(&UntypedActorRef) + Send + 'static,
202 {
203 if let Some(r) = self.current() {
204 deliver(&r);
205 return true;
206 }
207 let mut q = self.buffer.lock();
208 if q.len() >= self.buffer_size {
209 *self.drops.lock() += 1;
210 return false;
211 }
212 q.push_back(Box::new(deliver));
213 true
214 }
215
216 fn flush(&self, target: &UntypedActorRef) {
217 let mut q = self.buffer.lock();
218 while let Some(deliver) = q.pop_front() {
219 deliver(target);
220 }
221 }
222
223 pub fn buffered(&self) -> usize {
226 self.buffer.lock().len()
227 }
228
229 pub fn drops(&self) -> u64 {
231 *self.drops.lock()
232 }
233}
234
235pub struct ClusterSingletonProxy {
238 pub manager: Arc<ClusterSingletonManager>,
239}
240
241impl ClusterSingletonProxy {
242 pub fn new(manager: Arc<ClusterSingletonManager>) -> Self {
243 Self { manager }
244 }
245
246 pub fn singleton(&self) -> Option<UntypedActorRef> {
247 self.manager.current()
248 }
249
250 pub fn send<F>(&self, deliver: F) -> bool
254 where
255 F: FnOnce(&UntypedActorRef) + Send + 'static,
256 {
257 self.manager.buffer_or_deliver(deliver)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use atomr_core::actor::Inbox;
265 use std::sync::atomic::{AtomicU32, Ordering};
266
267 #[test]
268 fn proxy_routes_to_current_singleton() {
269 let mgr = ClusterSingletonManager::new();
270 let inbox = Inbox::<u32>::new("singleton");
271 mgr.set_active_here(inbox.actor_ref().as_untyped());
272 let proxy = ClusterSingletonProxy::new(mgr);
273 assert!(proxy.singleton().is_some());
274 }
275
276 #[test]
277 fn handover_state_transitions() {
278 let mgr = ClusterSingletonManager::new();
279 assert!(matches!(mgr.state(), SingletonState::Inactive));
280 mgr.begin_starting();
281 assert!(matches!(mgr.state(), SingletonState::Starting));
282 let inbox = Inbox::<u32>::new("s");
283 mgr.set_active_here(inbox.actor_ref().as_untyped());
284 assert!(matches!(mgr.state(), SingletonState::Active { here: true, .. }));
285 mgr.begin_handover();
286 assert!(matches!(mgr.state(), SingletonState::HandingOver));
287 }
288
289 #[tokio::test]
290 async fn proxy_buffers_during_handover_and_flushes_after() {
291 let mgr = ClusterSingletonManager::new();
292 let proxy = ClusterSingletonProxy::new(mgr.clone());
293
294 let calls = Arc::new(AtomicU32::new(0));
295 for _ in 0..3 {
297 let c = calls.clone();
298 assert!(proxy.send(move |_r| {
299 c.fetch_add(1, Ordering::SeqCst);
300 }));
301 }
302 assert_eq!(mgr.buffered(), 3);
303 assert_eq!(calls.load(Ordering::SeqCst), 0);
304
305 let inbox = Inbox::<u32>::new("s");
307 mgr.set_active_here(inbox.actor_ref().as_untyped());
308 assert_eq!(mgr.buffered(), 0);
309 assert_eq!(calls.load(Ordering::SeqCst), 3);
310
311 let c2 = calls.clone();
313 proxy.send(move |_| {
314 c2.fetch_add(1, Ordering::SeqCst);
315 });
316 assert_eq!(calls.load(Ordering::SeqCst), 4);
317 }
318
319 #[test]
320 fn full_buffer_drops_and_counts_overflow() {
321 let mgr = ClusterSingletonManager::with_buffer_size(2);
322 let proxy = ClusterSingletonProxy::new(mgr.clone());
323 assert!(proxy.send(|_| {}));
324 assert!(proxy.send(|_| {}));
325 assert!(!proxy.send(|_| {}));
327 assert_eq!(mgr.drops(), 1);
328 assert_eq!(mgr.buffered(), 2);
329 }
330
331 #[test]
332 fn fence_token_is_monotonic() {
333 let mgr = ClusterSingletonManager::new();
334 assert_eq!(mgr.fence(), FenceToken(0));
335 let inbox = Inbox::<u32>::new("s");
336 mgr.set_active_here(inbox.actor_ref().as_untyped());
337 let f1 = mgr.fence();
338 mgr.begin_handover();
339 mgr.set_active_here(inbox.actor_ref().as_untyped());
340 let f2 = mgr.fence();
341 assert!(f2 > f1, "fence must advance: {f1:?} -> {f2:?}");
342 assert_eq!(f1, FenceToken(1));
343 assert_eq!(f2, FenceToken(2));
344 }
345
346 #[test]
347 fn handoff_prepare_then_assume_carries_state_and_fence() {
348 use std::sync::atomic::AtomicU64;
349
350 struct Session {
351 assumed_fence: Arc<AtomicU64>,
353 assumed_prior: Arc<parking_lot::Mutex<Option<Vec<u8>>>>,
355 }
356 #[async_trait::async_trait]
357 impl SingletonHandoff for Session {
358 async fn prepare_handoff(&mut self) -> HandoffState {
359 HandoffState(b"session-42".to_vec())
360 }
361 async fn assume(&mut self, prior: Option<HandoffState>, fence: FenceToken) {
362 self.assumed_fence.store(fence.0, Ordering::SeqCst);
363 *self.assumed_prior.lock() = prior.map(|p| p.0);
364 }
365 }
366
367 let seen_fence = Arc::new(AtomicU64::new(0));
368 let seen_prior = Arc::new(parking_lot::Mutex::new(None));
369 let mgr = ClusterSingletonManager::with_handoff(Box::new(Session {
370 assumed_fence: seen_fence.clone(),
371 assumed_prior: seen_prior.clone(),
372 }));
373
374 let inbox = Inbox::<u32>::new("s");
375 mgr.set_active_here(inbox.actor_ref().as_untyped());
377 assert_eq!(seen_fence.load(Ordering::SeqCst), 1);
378 assert!(seen_prior.lock().is_none());
379
380 mgr.begin_handover();
382 mgr.set_active_here(inbox.actor_ref().as_untyped());
383 assert_eq!(seen_fence.load(Ordering::SeqCst), 2);
384 assert_eq!(seen_prior.lock().clone(), Some(b"session-42".to_vec()));
385 }
386
387 #[test]
388 fn handoff_buffering_still_works() {
389 struct Noop;
392 #[async_trait::async_trait]
393 impl SingletonHandoff for Noop {
394 async fn prepare_handoff(&mut self) -> HandoffState {
395 HandoffState(vec![])
396 }
397 async fn assume(&mut self, _prior: Option<HandoffState>, _fence: FenceToken) {}
398 }
399 let mgr = ClusterSingletonManager::with_handoff(Box::new(Noop));
400 let proxy = ClusterSingletonProxy::new(mgr.clone());
401 let calls = Arc::new(AtomicU32::new(0));
402 for _ in 0..2 {
403 let c = calls.clone();
404 assert!(proxy.send(move |_| {
405 c.fetch_add(1, Ordering::SeqCst);
406 }));
407 }
408 assert_eq!(mgr.buffered(), 2);
409 let inbox = Inbox::<u32>::new("s");
410 mgr.set_active_here(inbox.actor_ref().as_untyped());
411 assert_eq!(calls.load(Ordering::SeqCst), 2);
412 assert_eq!(mgr.buffered(), 0);
413 }
414
415 #[test]
416 fn set_active_remote_marks_here_false() {
417 let mgr = ClusterSingletonManager::new();
418 let inbox = Inbox::<u32>::new("remote-host");
419 mgr.set_active_remote(inbox.actor_ref().as_untyped());
420 match mgr.state() {
421 SingletonState::Active { here, .. } => assert!(!here),
422 _ => panic!("expected active-remote"),
423 }
424 }
425}