mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
turbo-persistence: Use AtomicBool for is_empty() to avoid lock contention (#92481)
### What? Replace the `RwLock`-based `is_empty()` check in `TurboPersistence` with a lock-free `AtomicBool` that mirrors the emptiness of `inner.meta_files`. ### Why? `is_empty()` is called on the hot read path (`lookup_task_candidates`) as a fast early-return before performing any real work. The previous implementation acquired a read lock on the inner `RwLock` purely to check whether `meta_files` is empty — a boolean state that changes infrequently (only on `load_directory` and `commit`). Taking a lock on every lookup, even a read lock, adds unnecessary contention and overhead. ### How? - Added an `is_empty: AtomicBool` field to `TurboPersistence`, initialized to `true` (a fresh database is always empty). - The atomic is updated at the two mutation points that change `meta_files`: - `load_directory()` — after reading meta files from disk into `inner.meta_files`. - `commit()` — after appending new meta files and retaining/removing old ones (under the write lock). - `is_empty()` now does a single `load(Ordering::Relaxed)` instead of acquiring a read lock. `Relaxed` ordering is appropriate because `is_empty()` is used as a performance hint, not a synchronization point. A briefly stale value only means a redundant hash computation, not a correctness issue. <!-- NEXT_JS_LLM_PR --> Co-authored-by: Tobias Koppers <[email protected]> Co-authored-by: Claude <[email protected]>
This commit is contained in:
co-authored by
Tobias Koppers
Claude
parent
e513488d0d
commit
dfa1201ab8
@@ -117,6 +117,9 @@ pub struct TurboPersistence<S: ParallelScheduler, const FAMILIES: usize> {
|
||||
read_only: bool,
|
||||
/// The inner state of the database. Writing will update that.
|
||||
inner: RwLock<Inner<FAMILIES>>,
|
||||
/// A flag to indicate if the database is empty (no meta files). This is an atomic mirror of
|
||||
/// `inner.meta_files.is_empty()` to avoid taking a lock on the hot path.
|
||||
is_empty: AtomicBool,
|
||||
/// A flag to indicate if a write operation is currently active. Prevents multiple concurrent
|
||||
/// write operations.
|
||||
active_write_operation: AtomicBool,
|
||||
@@ -191,6 +194,7 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
|
||||
accessed_key_hashes: [(); FAMILIES]
|
||||
.map(|_| DashSet::with_hasher(BuildNoHashHasher::default())),
|
||||
}),
|
||||
is_empty: AtomicBool::new(true),
|
||||
active_write_operation: AtomicBool::new(false),
|
||||
key_block_cache: BlockCache::with(
|
||||
KEY_BLOCK_CACHE_SIZE as usize / KEY_BLOCK_AVG_SIZE,
|
||||
@@ -395,6 +399,8 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
|
||||
}
|
||||
|
||||
let inner = self.inner.get_mut();
|
||||
self.is_empty
|
||||
.store(meta_files.is_empty(), Ordering::Relaxed);
|
||||
inner.meta_files = meta_files;
|
||||
inner.current_sequence_number = current;
|
||||
Ok(true)
|
||||
@@ -442,7 +448,7 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
|
||||
|
||||
/// Returns true if the database is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.read().meta_files.is_empty()
|
||||
self.is_empty.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Starts a new WriteBatch for the database. Only a single write operation is allowed at a
|
||||
@@ -686,6 +692,8 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
|
||||
}
|
||||
});
|
||||
inner.meta_files.reverse();
|
||||
self.is_empty
|
||||
.store(inner.meta_files.is_empty(), Ordering::Relaxed);
|
||||
has_delete_file = !sst_seq_numbers_to_delete.is_empty()
|
||||
|| !blob_seq_numbers_to_delete.is_empty()
|
||||
|| !meta_seq_numbers_to_delete.is_empty();
|
||||
|
||||
Reference in New Issue
Block a user