parse the keydb outside the cache lock

cached_db held self.cache locked across the full KeyDb::load_counted parse of the ~62 MiB keydb, so a reparse (triggered by a changed mtime) stalled every other worker waiting on the cache. Take a brief lock for the fast-path settled-cache-hit check, release it, parse outside the lock, then re-acquire and double-check: if a peer installed the same settled stamp while we parsed, adopt their entry and drop our redundant parse rather than racing a lost update. Stamp and bytes still come from the one already-open file handle, preserving the one-open-one-identity invariant.
This commit is contained in:
Matthew Jackson
2026-09-05 12:37:59 -07:00
parent 7c544e3780
commit 1c29377780
+21 -5
View File
@@ -174,20 +174,36 @@ impl KeydbSource {
let file = std::fs::File::open(&self.path)?; let file = std::fs::File::open(&self.path)?;
let stamp = FileStamp::of(&file.metadata()?); let stamp = FileStamp::of(&file.metadata()?);
let stamped_at = std::time::SystemTime::now(); let stamped_at = std::time::SystemTime::now();
// Fast path: a settled cache hit under a brief lock (stamp compare +
// Arc clone). Re-emit the rejection summary on EVERY hit — a hit skips
// `KeyDb::parse`, else a corrupt keydb warns once then serves silently.
{
let guard = self.cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(entry) = guard.as_ref()
&& entry.stamp == stamp
&& entry.is_settled(self.mtime_granularity)
{
self.emit_parse_stats(&entry.stats);
return Ok(entry.db.clone());
}
}
// Miss: parse the ~62 MiB file OUTSIDE the lock so a reparse can't stall
// every other worker. Stamp (fstat) and bytes still come from the ONE
// open handle, preserving the one-open-one-identity invariant.
let (db, stats) = KeyDb::load_counted(file, &self.path)?;
let db = Arc::new(db);
// Re-acquire and double-check: a peer may have installed the same
// settled stamp while we parsed — adopt theirs and drop our redundant
// parse rather than racing a lost update.
let mut guard = self.cache.lock().unwrap_or_else(|e| e.into_inner()); let mut guard = self.cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(entry) = guard.as_ref() if let Some(entry) = guard.as_ref()
&& entry.stamp == stamp && entry.stamp == stamp
&& entry.is_settled(self.mtime_granularity) && entry.is_settled(self.mtime_granularity)
{ {
// Re-emit the rejection summary on EVERY hit, not just the parse
// that produced it (a hit skips `KeyDb::parse`) — otherwise a
// corrupt keydb warns once, then serves silently. See docs/keydb.md#cached-db-reemit.
self.emit_parse_stats(&entry.stats); self.emit_parse_stats(&entry.stats);
return Ok(entry.db.clone()); return Ok(entry.db.clone());
} }
let (db, stats) = KeyDb::load_counted(file, &self.path)?;
self.emit_parse_stats(&stats); self.emit_parse_stats(&stats);
let db = Arc::new(db);
self.parses.fetch_add(1, Ordering::Relaxed); self.parses.fetch_add(1, Ordering::Relaxed);
*guard = Some(CacheEntry { *guard = Some(CacheEntry {
stamp, stamp,