use std::sync::{Arc, Mutex};
use nodedb_types::sync::compensation::CompensationHint;
#[derive(Debug, Clone)]
pub struct CompensationEvent {
pub mutation_id: u64,
pub collection: String,
pub document_id: String,
pub hint: CompensationHint,
}
pub trait CompensationHandler: Send + Sync + 'static {
fn on_compensation(&self, event: CompensationEvent);
}
impl<F: Fn(CompensationEvent) + Send + Sync + 'static> CompensationHandler for F {
fn on_compensation(&self, event: CompensationEvent) {
self(event);
}
}
pub struct CompensationRegistry {
handler: Mutex<Option<Arc<dyn CompensationHandler>>>,
buffered: Mutex<Vec<CompensationEvent>>,
max_buffer: usize,
}
impl CompensationRegistry {
pub fn new() -> Self {
Self {
handler: Mutex::new(None),
buffered: Mutex::new(Vec::new()),
max_buffer: 1000,
}
}
pub fn set_handler(&self, handler: Arc<dyn CompensationHandler>) {
if let Ok(mut buf) = self.buffered.lock() {
for event in buf.drain(..) {
handler.on_compensation(event);
}
}
if let Ok(mut h) = self.handler.lock() {
*h = Some(handler);
}
}
pub fn clear_handler(&self) {
if let Ok(mut h) = self.handler.lock() {
*h = None;
}
}
pub fn dispatch(&self, event: CompensationEvent) {
if let Ok(h) = self.handler.lock()
&& let Some(handler) = h.as_ref()
{
handler.on_compensation(event);
return;
}
if let Ok(mut buf) = self.buffered.lock()
&& buf.len() < self.max_buffer
{
buf.push(event);
}
}
pub fn buffered_count(&self) -> usize {
self.buffered.lock().map(|b| b.len()).unwrap_or(0)
}
}
impl Default for CompensationRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
fn make_event(mutation_id: u64) -> CompensationEvent {
CompensationEvent {
mutation_id,
collection: "users".into(),
document_id: "u1".into(),
hint: CompensationHint::UniqueViolation {
field: "email".into(),
conflicting_value: "a@b.com".into(),
},
}
}
#[test]
fn dispatch_with_handler() {
let count = Arc::new(AtomicU32::new(0));
let count_clone = count.clone();
let registry = CompensationRegistry::new();
registry.set_handler(Arc::new(move |_: CompensationEvent| {
count_clone.fetch_add(1, Ordering::Relaxed);
}));
registry.dispatch(make_event(1));
registry.dispatch(make_event(2));
assert_eq!(count.load(Ordering::Relaxed), 2);
}
#[test]
fn dispatch_without_handler_buffers() {
let registry = CompensationRegistry::new();
registry.dispatch(make_event(1));
registry.dispatch(make_event(2));
assert_eq!(registry.buffered_count(), 2);
}
#[test]
fn late_handler_drains_buffer() {
let count = Arc::new(AtomicU32::new(0));
let count_clone = count.clone();
let registry = CompensationRegistry::new();
registry.dispatch(make_event(1));
registry.dispatch(make_event(2));
assert_eq!(registry.buffered_count(), 2);
registry.set_handler(Arc::new(move |_: CompensationEvent| {
count_clone.fetch_add(1, Ordering::Relaxed);
}));
assert_eq!(count.load(Ordering::Relaxed), 2);
assert_eq!(registry.buffered_count(), 0);
registry.dispatch(make_event(3));
assert_eq!(count.load(Ordering::Relaxed), 3);
}
#[test]
fn clear_handler_resumes_buffering() {
let registry = CompensationRegistry::new();
let count = Arc::new(AtomicU32::new(0));
let count_clone = count.clone();
registry.set_handler(Arc::new(move |_: CompensationEvent| {
count_clone.fetch_add(1, Ordering::Relaxed);
}));
registry.dispatch(make_event(1));
assert_eq!(count.load(Ordering::Relaxed), 1);
registry.clear_handler();
registry.dispatch(make_event(2));
assert_eq!(count.load(Ordering::Relaxed), 1); assert_eq!(registry.buffered_count(), 1);
}
}