Push new log entries to peers on commit

A broadcast channel forwards each freshly committed entry straight to
connected peers, so a registration propagates in milliseconds. The
periodic digest drops to a 10s anti-entropy backstop.
This commit is contained in:
Jean Chevronnet 2026-07-12 07:20:25 +00:00
parent d0556ebe8c
commit df7b413862
No known key found for this signature in database
3 changed files with 101 additions and 25 deletions

View file

@ -7,6 +7,7 @@ use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
use super::scram::{self, Hash};
@ -66,11 +67,12 @@ pub struct EventLog {
lamport: u64, // logical clock, ticked on every event
versions: HashMap<String, u64>, // per-origin highest seq applied (version vector)
entries: Vec<LogEntry>, // full log, kept so peers can pull what they lack
outbound: Option<broadcast::Sender<LogEntry>>, // push newly committed entries to peers
}
impl EventLog {
fn open(path: PathBuf, origin: String) -> (Self, Vec<Event>) {
let mut log = Self { path, origin, lamport: 0, versions: HashMap::new(), entries: Vec::new() };
let mut log = Self { path, origin, lamport: 0, versions: HashMap::new(), entries: Vec::new(), outbound: None };
if let Ok(data) = std::fs::read_to_string(&log.path) {
for line in data.lines().filter(|l| !l.trim().is_empty()) {
match serde_json::from_str::<LogEntry>(line) {
@ -104,6 +106,7 @@ impl EventLog {
let entry = LogEntry { origin: self.origin.clone(), seq: self.next_seq(), lamport: self.lamport, event };
self.persist(&entry)?;
self.versions.insert(entry.origin.clone(), entry.seq);
self.notify(&entry);
self.entries.push(entry);
Ok(())
}
@ -119,10 +122,23 @@ impl EventLog {
self.lamport = self.lamport.max(entry.lamport) + 1; // Lamport receive rule
self.versions.insert(entry.origin.clone(), entry.seq);
let event = entry.event.clone();
self.notify(&entry);
self.entries.push(entry);
Ok(Some(event))
}
// Push a freshly committed entry to connected peers, if any are wired up.
// Best effort: a lagging subscriber just relies on the periodic digest.
fn notify(&self, entry: &LogEntry) {
if let Some(tx) = &self.outbound {
let _ = tx.send(entry.clone());
}
}
fn set_outbound(&mut self, tx: broadcast::Sender<LogEntry>) {
self.outbound = Some(tx);
}
// Our version vector: highest seq applied per origin.
fn version_vector(&self) -> HashMap<String, u64> {
self.versions.clone()
@ -208,6 +224,11 @@ impl Db {
Ok(())
}
/// Wire the log to a broadcast channel; each new entry is pushed to peers.
pub fn set_outbound(&mut self, tx: broadcast::Sender<LogEntry>) {
self.log.set_outbound(tx);
}
/// Our version vector, advertised to peers so they can send what we lack.
pub fn version_vector(&self) -> HashMap<String, u64> {
self.log.version_vector()