//! Activity-status bitmap. Owned by [`any::Db`](super::any::db::Db) and shared with live //! [`MerkleizedBatch`](super::current::batch::MerkleizedBatch)es via `Arc>`. //! //! `any::Db` mutates the inner [`bitmap::Prunable`] under a [`RwLock`] during `apply_batch` / //! `prune` / `rewind` while live batches read concurrently. Locking (not snapshotting) keeps //! memory at O(bitmap size); snapshots would couple memory to live-batch count and lifetime. //! //! Reads through an invalidated `MerkleizedBatch` (see its "Branch validity" docs) return //! inconsistent bytes; callers must drop invalid batches. #[cfg(test)] use commonware_utils::bitmap::Readable as _; use commonware_utils::{ bitmap, sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, }; pub(crate) struct Shared { inner: RwLock>, } impl Shared { pub(crate) const fn new(bitmap: bitmap::Prunable) -> Self { Self { inner: RwLock::new(bitmap), } } /// Acquire a shared read guard over the committed bitmap. Kept private so external callers /// go through [`bitmap::Readable`] (which doesn't expose a guard across `.await`). fn read(&self) -> RwLockReadGuard<'_, bitmap::Prunable> { self.inner.read() } /// Acquire an exclusive write guard. By convention only the inner-`any` mutators /// (`apply_batch`, `prune_bitmap`, `rewind`) hold the write lock. pub(crate) fn write(&self) -> RwLockWriteGuard<'_, bitmap::Prunable> { self.inner.write() } /// Single-lock alternative to `bitmap::Readable::ones_iter_from(from).next()`. #[cfg(test)] pub(crate) fn next_one_from(&self, from: u64) -> Option { self.read().ones_iter_from(from).next() } /// Fill `out` with up to `limit` floor-raise candidates in `[scan_from, tip)`, holding a single /// read guard for the whole batch. Returns the next `scan_from`. /// /// The candidate sequence is identical to repeatedly calling `any::batch::next_candidate` /// (the test oracle): set bits in the committed prefix are returned in order via one /// `ones_iter_from`, then locations at or beyond the committed boundary are returned /// sequentially. pub(crate) fn fill_candidates>( &self, scan_from: u64, tip: u64, limit: usize, out: &mut Vec, ) -> u64 { fill_from(&*self.read(), scan_from, tip, limit, out) } /// Return the number of pruned bits. Acquires the read lock briefly. #[cfg(any(test, feature = "test-traits"))] pub(crate) fn pruned_bits(&self) -> u64 { self.read().pruned_bits() } /// Return the value of the bit at `loc`. Acquires the read lock briefly. #[cfg(any(test, feature = "test-traits"))] pub(crate) fn get_bit(&self, loc: u64) -> bool { self.read().get_bit(loc) } } /// Core floor-raise scan over any [`bitmap::Readable`]: set bits in `[scan_from, min(len, tip))` /// ascending via one `ones_iter_from`, then locations in `[max(scan_from, len), tip)` /// sequentially. Fills `out` with up to `limit` candidates and returns the next `scan_from`. /// /// The bitmap is read once per chunk (through the iterator), so a `B` whose reads go through /// interior mutability must not be mutated for the duration of the call. pub(crate) fn fill_from, T: From, const N: usize>( bitmap: &B, scan_from: u64, tip: u64, limit: usize, out: &mut Vec, ) -> u64 { let bitmap_len = bitmap.len(); let committed_end = bitmap_len.min(tip); let mut scan = scan_from; if scan < committed_end { let mut ones = bitmap.ones_iter_from(scan); while out.len() < limit { match ones.next() { Some(idx) if idx < committed_end => { out.push(idx.into()); scan = idx + 1; } _ => break, } } } while out.len() < limit { let candidate = scan.max(bitmap_len); if candidate >= tip { // Advance only through the span the ones scan verified clear. When `tip < len` // (a layered bitmap scanned with a committed-boundary tip), bits in // `[committed_end, len)` were never examined and a later call with a larger // `tip` must still see them. scan = scan.max(committed_end); break; } out.push(candidate.into()); scan = candidate + 1; } scan } impl std::fmt::Debug for Shared { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Shared") .field("bitmap_len", &bitmap::Readable::::len(&*self.read())) .finish() } } /// [`bitmap::Readable`] over the DB's committed bitmap. Each call acquires the read lock briefly. impl bitmap::Readable for Shared { fn complete_chunks(&self) -> usize { self.read().complete_chunks() } fn get_chunk(&self, idx: usize) -> [u8; N] { *self.read().get_chunk(idx) } fn last_chunk(&self) -> ([u8; N], u64) { let guard = self.read(); let (chunk, bits) = guard.last_chunk(); (*chunk, bits) } fn pruned_chunks(&self) -> usize { self.read().pruned_chunks() } fn len(&self) -> u64 { bitmap::Readable::::len(&*self.read()) } }