nodedb_lite/sync/
compensation.rs1use std::sync::{Arc, Mutex};
8
9use nodedb_types::sync::compensation::CompensationHint;
10
11#[derive(Debug, Clone)]
13pub struct CompensationEvent {
14 pub mutation_id: u64,
16 pub collection: String,
18 pub document_id: String,
20 pub hint: CompensationHint,
22}
23
24pub trait CompensationHandler: Send + Sync + 'static {
31 fn on_compensation(&self, event: CompensationEvent);
32}
33
34impl<F: Fn(CompensationEvent) + Send + Sync + 'static> CompensationHandler for F {
36 fn on_compensation(&self, event: CompensationEvent) {
37 self(event);
38 }
39}
40
41pub struct CompensationRegistry {
46 handler: Mutex<Option<Arc<dyn CompensationHandler>>>,
47 buffered: Mutex<Vec<CompensationEvent>>,
49 max_buffer: usize,
51}
52
53impl CompensationRegistry {
54 pub fn new() -> Self {
55 Self {
56 handler: Mutex::new(None),
57 buffered: Mutex::new(Vec::new()),
58 max_buffer: 1000,
59 }
60 }
61
62 pub fn set_handler(&self, handler: Arc<dyn CompensationHandler>) {
64 if let Ok(mut buf) = self.buffered.lock() {
66 for event in buf.drain(..) {
67 handler.on_compensation(event);
68 }
69 }
70 if let Ok(mut h) = self.handler.lock() {
71 *h = Some(handler);
72 }
73 }
74
75 pub fn clear_handler(&self) {
77 if let Ok(mut h) = self.handler.lock() {
78 *h = None;
79 }
80 }
81
82 pub fn dispatch(&self, event: CompensationEvent) {
84 if let Ok(h) = self.handler.lock()
85 && let Some(handler) = h.as_ref()
86 {
87 handler.on_compensation(event);
88 return;
89 }
90 if let Ok(mut buf) = self.buffered.lock()
92 && buf.len() < self.max_buffer
93 {
94 buf.push(event);
95 }
96 }
97
98 pub fn buffered_count(&self) -> usize {
100 self.buffered.lock().map(|b| b.len()).unwrap_or(0)
101 }
102}
103
104impl Default for CompensationRegistry {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use std::sync::atomic::{AtomicU32, Ordering};
114
115 fn make_event(mutation_id: u64) -> CompensationEvent {
116 CompensationEvent {
117 mutation_id,
118 collection: "users".into(),
119 document_id: "u1".into(),
120 hint: CompensationHint::UniqueViolation {
121 field: "email".into(),
122 conflicting_value: "a@b.com".into(),
123 },
124 }
125 }
126
127 #[test]
128 fn dispatch_with_handler() {
129 let count = Arc::new(AtomicU32::new(0));
130 let count_clone = count.clone();
131 let registry = CompensationRegistry::new();
132
133 registry.set_handler(Arc::new(move |_: CompensationEvent| {
134 count_clone.fetch_add(1, Ordering::Relaxed);
135 }));
136
137 registry.dispatch(make_event(1));
138 registry.dispatch(make_event(2));
139
140 assert_eq!(count.load(Ordering::Relaxed), 2);
141 }
142
143 #[test]
144 fn dispatch_without_handler_buffers() {
145 let registry = CompensationRegistry::new();
146
147 registry.dispatch(make_event(1));
148 registry.dispatch(make_event(2));
149
150 assert_eq!(registry.buffered_count(), 2);
151 }
152
153 #[test]
154 fn late_handler_drains_buffer() {
155 let count = Arc::new(AtomicU32::new(0));
156 let count_clone = count.clone();
157 let registry = CompensationRegistry::new();
158
159 registry.dispatch(make_event(1));
161 registry.dispatch(make_event(2));
162 assert_eq!(registry.buffered_count(), 2);
163
164 registry.set_handler(Arc::new(move |_: CompensationEvent| {
166 count_clone.fetch_add(1, Ordering::Relaxed);
167 }));
168
169 assert_eq!(count.load(Ordering::Relaxed), 2);
170 assert_eq!(registry.buffered_count(), 0);
171
172 registry.dispatch(make_event(3));
174 assert_eq!(count.load(Ordering::Relaxed), 3);
175 }
176
177 #[test]
178 fn clear_handler_resumes_buffering() {
179 let registry = CompensationRegistry::new();
180 let count = Arc::new(AtomicU32::new(0));
181 let count_clone = count.clone();
182
183 registry.set_handler(Arc::new(move |_: CompensationEvent| {
184 count_clone.fetch_add(1, Ordering::Relaxed);
185 }));
186
187 registry.dispatch(make_event(1));
188 assert_eq!(count.load(Ordering::Relaxed), 1);
189
190 registry.clear_handler();
191 registry.dispatch(make_event(2));
192 assert_eq!(count.load(Ordering::Relaxed), 1); assert_eq!(registry.buffered_count(), 1);
194 }
195}