use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::HashMap;
use crate::vlog::{BlobFileId, ValueHandle};
#[cfg(feature = "std")]
use parking_lot::Mutex;
#[cfg(not(feature = "std"))]
use spin::Mutex;
type VersionId = u64;
pub type OwnedObjects = Arc<[(BlobFileId, u64)]>;
#[derive(Default)]
struct Records {
readers: BTreeMap<VersionId, usize>,
objects: HashMap<ValueHandle, VersionId>,
tables: Vec<(VersionId, OwnedObjects)>,
}
impl Records {
fn prune(&mut self) {
let Some(&oldest) = self.readers.keys().next() else {
self.objects.clear();
self.tables.clear();
return;
};
self.objects.retain(|_, at| *at > oldest);
self.tables.retain(|(at, _)| *at > oldest);
}
}
fn note<K: core::hash::Hash + Eq>(map: &mut HashMap<K, VersionId>, key: K, version: VersionId) {
let at = map.entry(key).or_insert(version);
*at = (*at).max(version);
}
#[derive(Default)]
pub struct ReleasedObjects {
records: Mutex<Records>,
}
impl ReleasedObjects {
pub fn register(self: &Arc<Self>, version: VersionId) -> ReaderToken {
*self.records.lock().readers.entry(version).or_insert(0) += 1;
ReaderToken {
registry: Arc::clone(self),
version,
}
}
pub fn holds_reads(&self) -> bool {
!self.records.lock().readers.is_empty()
}
pub fn record(
&self,
version: VersionId,
objects: impl IntoIterator<Item = ValueHandle>,
tables: impl IntoIterator<Item = OwnedObjects>,
) {
let mut records = self.records.lock();
if records
.readers
.keys()
.next()
.is_none_or(|&oldest| oldest >= version)
{
return;
}
for handle in objects {
note(&mut records.objects, handle, version);
}
for owned in tables {
if !owned.is_empty() {
records.tables.push((version, owned));
}
}
}
pub fn released_since(&self, read: VersionId, handle: &ValueHandle) -> bool {
let records = self.records.lock();
let object = (handle.blob_file_id, handle.offset);
records.objects.get(handle).is_some_and(|&at| at > read)
|| records
.tables
.iter()
.any(|(at, owned)| *at > read && owned.binary_search(&object).is_ok())
}
}
pub struct ReaderToken {
registry: Arc<ReleasedObjects>,
version: VersionId,
}
impl ReaderToken {
#[must_use]
pub fn version(&self) -> VersionId {
self.version
}
}
impl Drop for ReaderToken {
fn drop(&mut self) {
let mut records = self.registry.records.lock();
if let Some(count) = records.readers.get_mut(&self.version) {
*count -= 1;
if *count == 0 {
records.readers.remove(&self.version);
}
}
records.prune();
}
}
#[cfg(test)]
mod tests;