pub struct ReadAheadPool { /* private fields */ }Expand description
The process-wide read-ahead allocator: one byte budget shared by all active
streams, with try_lock LRU eviction. Deadlock-free by construction — the
budget is a lock-free atomic, the registry lock is a leaf (released before any
buffer mutex), and eviction never blocks on a buffer mutex (try_lock + skip).
Implementations§
Source§impl ReadAheadPool
impl ReadAheadPool
pub fn new(budget: u64) -> Self
pub fn enabled(&self) -> bool
Sourcepub fn per_stream_cap(&self) -> u64
pub fn per_stream_cap(&self) -> u64
Per-stream window cap derived from the budget.
Sourcepub fn register(&self, key: usize, buf: Arc<Mutex<ReadAhead>>)
pub fn register(&self, key: usize, buf: Arc<Mutex<ReadAhead>>)
Lazily register a handle’s buffer once sequential access is detected.
Sourcepub fn deregister(&self, key: usize)
pub fn deregister(&self, key: usize)
Deregister on release; uncharges the buffer’s bytes. Drops the streams
lock BEFORE locking the buffer: a concurrent read holds its buffer mutex
and then blocking-acquires streams (via permitted_window), so holding
streams while locking the buffer here would invert that order and
deadlock. Keeping streams a leaf (released before any buffer mutex)
preserves the pool’s deadlock-free invariant.
Sourcepub fn touch(&self, key: usize)
pub fn touch(&self, key: usize)
Mark key as most-recently-served (LRU bump). No-op if unregistered.
Best-effort: a try_lock that SKIPS rather than blocks on contention. It
runs at the end of every backing pread, on every stream, so a blocking
lock here serializes all concurrent playback on this one registry mutex
(#519). A skipped bump only leaves a stamp slightly stale, at worst evicting
a marginally-warmer stream — and eviction is already best-effort
(try_lock + skip), so the LRU order was never exact to begin with.
Sourcepub fn permitted_window(&self, key: usize, old_len: u64, desired: u64) -> u64
pub fn permitted_window(&self, key: usize, old_len: u64, desired: u64) -> u64
Decide the largest window (≤ desired, ≤ per-stream cap) a stream may grow
to right now, given a current size of old_len. Evicts colder OTHER
streams as needed to make room for the (window - old_len) delta, but does
NOT charge — charging happens in reconcile against the ACTUAL bytes read.
Never blocks on a buffer mutex (try_lock + skip). Call only on a miss.
Sourcepub fn reconcile(&self, old_len: u64, new_len: u64)
pub fn reconcile(&self, old_len: u64, new_len: u64)
Charge the budget by the ACTUAL window-size change (old_len → new_len).
Keeps the invariant charged == Σ(registered buffers' bytes.len()).
Sourcepub fn has_room_for(&self, need: u64) -> bool
pub fn has_room_for(&self, need: u64) -> bool
Best-effort check that need bytes of free (uncharged) budget exist.
Speculative prefetch uses this rather than evicting live streams: under
memory pressure it simply declines to prefetch. Racy by design — the
caller still reconciles the actual stored delta, so the
charged == Σ bytes invariant holds regardless; this only bounds overshoot.