core: Server::disk_write — coalescing off-core snapshot writer; reputation/metadata saves no longer block the event loop on a slow disk

This commit is contained in:
Jean Chevronnet 2026-08-12 13:16:15 +00:00
parent c231c6b8ef
commit 145a01b2c2
3 changed files with 39 additions and 2 deletions

View file

@ -228,7 +228,7 @@ pub fn save(s: &Server) {
} }
} }
} }
let _ = std::fs::write(db_path(s), out); s.disk_write(db_path(s), out); // off-core: a slow disk mustn't stall the event loop
} }
/// Reload persisted channel metadata at startup. /// Reload persisted channel metadata at startup.

View file

@ -308,7 +308,7 @@ pub fn save(s: &Server) {
out.push_str(&format!("{ip} {} {}\n", e.score, e.last_seen)); out.push_str(&format!("{ip} {} {}\n", e.score, e.last_seen));
} }
} }
let _ = std::fs::write(db_path(s), out); s.disk_write(db_path(s), out); // off-core: a slow disk mustn't stall the event loop
} }
/// Reload persisted reputation at startup. /// Reload persisted reputation at startup.

View file

@ -485,6 +485,43 @@ impl Server {
true true
} }
/// Fire-and-forget full-snapshot write of `contents` to `path`. The core thread
/// hands the buffer to a dedicated background writer and returns immediately, so a
/// slow or full disk never stalls the event loop. Writes coalesce per path (newest
/// content wins), so a backed-up writer stays bounded at one pending snapshot per
/// file — safe precisely because each write is the complete current state.
pub fn disk_write(&self, path: String, contents: String) {
use std::collections::HashMap;
use std::sync::{Condvar, Mutex, OnceLock};
type Pending = std::sync::Arc<(Mutex<HashMap<String, String>>, Condvar)>;
static WRITER: OnceLock<Pending> = OnceLock::new();
let pending = WRITER.get_or_init(|| {
let p: Pending = std::sync::Arc::new((Mutex::new(HashMap::new()), Condvar::new()));
let worker = p.clone();
std::thread::spawn(move || {
let (lock, cv) = &*worker;
loop {
let batch = {
let mut map = lock.lock().unwrap_or_else(|e| e.into_inner());
while map.is_empty() {
map = cv.wait(map).unwrap_or_else(|e| e.into_inner());
}
std::mem::take(&mut *map) // drain, releasing the lock before writing
};
for (path, contents) in batch {
let _ = std::fs::write(path, contents);
}
}
});
p
});
let (lock, cv) = &**pending;
if let Ok(mut map) = lock.lock() {
map.insert(path, contents);
cv.notify_one();
}
}
/// A pre-registration `:server NOTICE * :*** <msg>` line. /// A pre-registration `:server NOTICE * :*** <msg>` line.
pub(crate) fn notice_star(&self, uid: Uid, msg: &str) { pub(crate) fn notice_star(&self, uid: Uid, msg: &str) {
self.send(uid, format!(":{} NOTICE * :*** {msg}", self.name)); self.send(uid, format!(":{} NOTICE * :*** {msg}", self.name));