mirror of
https://github.com/freemkv/freemkv-keysources.git
synced 2026-10-07 18:13:11 +00:00
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:
+21
-5
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user