v0.0.8: Server-side message deduplication
Server: - DedupTracker in AppState: bounded HashSet (10,000 IDs, FIFO eviction) - send_message: extracts message ID from bincode, drops duplicates - WS handler: dedup on both binary and JSON message frames - extract_message_id() parses all WireMessage variants Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,57 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
/// Maximum number of message IDs to track for deduplication.
|
||||
const DEDUP_CAPACITY: usize = 10_000;
|
||||
|
||||
/// Per-connection sender: messages are pushed here for instant delivery.
|
||||
pub type WsSender = mpsc::UnboundedSender<Vec<u8>>;
|
||||
|
||||
/// Connected clients: fingerprint → list of WS senders (multiple devices).
|
||||
pub type Connections = Arc<Mutex<HashMap<String, Vec<WsSender>>>>;
|
||||
|
||||
/// Bounded dedup tracker: FIFO eviction when capacity is exceeded.
|
||||
#[derive(Clone)]
|
||||
pub struct DedupTracker {
|
||||
seen: Arc<std::sync::Mutex<HashSet<String>>>,
|
||||
order: Arc<std::sync::Mutex<VecDeque<String>>>,
|
||||
}
|
||||
|
||||
impl DedupTracker {
|
||||
pub fn new() -> Self {
|
||||
DedupTracker {
|
||||
seen: Arc::new(std::sync::Mutex::new(HashSet::with_capacity(DEDUP_CAPACITY))),
|
||||
order: Arc::new(std::sync::Mutex::new(VecDeque::with_capacity(DEDUP_CAPACITY))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this ID was already seen (i.e. it is a duplicate).
|
||||
/// If new, inserts it and evicts the oldest if over capacity.
|
||||
pub fn check_and_insert(&self, id: &str) -> bool {
|
||||
let mut seen = self.seen.lock().unwrap();
|
||||
if seen.contains(id) {
|
||||
return true; // duplicate
|
||||
}
|
||||
let mut order = self.order.lock().unwrap();
|
||||
if seen.len() >= DEDUP_CAPACITY {
|
||||
if let Some(oldest) = order.pop_front() {
|
||||
seen.remove(&oldest);
|
||||
}
|
||||
}
|
||||
seen.insert(id.to_string());
|
||||
order.push_back(id.to_string());
|
||||
false // not a duplicate
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: Arc<Database>,
|
||||
pub connections: Connections,
|
||||
pub dedup: DedupTracker,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -22,6 +60,7 @@ impl AppState {
|
||||
Ok(AppState {
|
||||
db: Arc::new(db),
|
||||
connections: Arc::new(Mutex::new(HashMap::new())),
|
||||
dedup: DedupTracker::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user