use crate::{
MAX_SEQNO, SeqNo, SharedSequenceNumberGenerator,
comparator::SharedComparator,
fs::{Fs, SyncMode},
memtable::Memtable,
tree::sealed::SealedMemtables,
version::{Version, VersionId, edit_log, persist_version},
};
fn remove_if_present(fs: &dyn Fs, path: &Path) -> crate::Result<()> {
match fs.remove_file(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == crate::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e.into()),
}
}
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use arc_swap::ArcSwap;
use crate::path::Path;
#[derive(Clone)]
pub struct SuperVersion {
#[doc(hidden)]
pub active_memtable: Arc<Memtable>,
pub(crate) sealed_memtables: Arc<SealedMemtables>,
pub(crate) version: Version,
pub(crate) seqno: SeqNo,
}
pub enum SnapshotRef {
#[cfg(feature = "std")]
Latest(arc_swap::Guard<Arc<SuperVersion>>),
#[cfg(not(feature = "std"))]
Owned(SuperVersion),
}
impl core::ops::Deref for SnapshotRef {
type Target = SuperVersion;
fn deref(&self) -> &SuperVersion {
match self {
#[cfg(feature = "std")]
Self::Latest(guard) => guard,
#[cfg(not(feature = "std"))]
Self::Owned(version) => version,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum RetentionEffect {
Keep,
GcBelow(SeqNo),
DropsData,
}
impl RetentionEffect {
pub(crate) fn of_run(
filter_transformed: bool,
collected_below_watermark: bool,
watermark: SeqNo,
) -> Self {
if filter_transformed {
Self::DropsData
} else if collected_below_watermark {
Self::GcBelow(watermark)
} else {
Self::Keep
}
}
}
pub struct SuperVersions {
current: SuperVersion,
comparator_name: Arc<str>,
sync_mode: SyncMode,
snapshot_id: VersionId,
log_rotate_bytes: u64,
log_bytes: Option<u64>,
edit_scratch: Vec<u8>,
#[cfg(feature = "std")]
latest: Arc<ArcSwap<SuperVersion>>,
}
impl SuperVersions {
pub fn new(
version: Version,
comparator: &SharedComparator,
sync_mode: SyncMode,
snapshot_id: VersionId,
log_rotate_bytes: u64,
) -> Self {
let comparator_name: Arc<str> = comparator.name().into();
let initial = SuperVersion {
active_memtable: Arc::new(Memtable::new(0, comparator.clone())),
sealed_memtables: Arc::default(),
seqno: version.retention_floor(),
version,
};
Self {
#[cfg(feature = "std")]
latest: Arc::new(ArcSwap::from_pointee(initial.clone())),
current: initial,
comparator_name,
sync_mode,
snapshot_id,
log_rotate_bytes,
log_bytes: None,
edit_scratch: Vec::new(),
}
}
#[expect(
clippy::too_many_arguments,
reason = "version upgrade threads tree_path, mutator closure, two seqno gens, fs, \
runtime, encryption, retention effect: every parameter is load-bearing \
per the manifest-persist contract"
)]
pub(crate) fn upgrade_version<F: FnOnce(&SuperVersion) -> crate::Result<SuperVersion>>(
&mut self,
tree_path: &Path,
f: F,
seqno: &SharedSequenceNumberGenerator,
visible_seqno: &SharedSequenceNumberGenerator,
fs: &dyn Fs,
runtime: Arc<crate::runtime_config::RuntimeConfig>,
encryption: Option<Arc<dyn crate::encryption::EncryptionProvider>>,
retention: RetentionEffect,
) -> crate::Result<()> {
self.upgrade_version_with_seqno(
tree_path,
f,
seqno.next(),
visible_seqno,
fs,
runtime,
encryption,
retention,
)
}
#[expect(
clippy::too_many_arguments,
reason = "version upgrade with pre-allocated seqno: tree_path, mutator, seqno, \
visible_seqno, fs, runtime, encryption, retention effect; same \
load-bearing surface as the auto-allocating sibling above"
)]
pub(crate) fn upgrade_version_with_seqno<
F: FnOnce(&SuperVersion) -> crate::Result<SuperVersion>,
>(
&mut self,
tree_path: &Path,
f: F,
seqno: SeqNo,
visible_seqno: &SharedSequenceNumberGenerator,
fs: &dyn Fs,
runtime: Arc<crate::runtime_config::RuntimeConfig>,
encryption: Option<Arc<dyn crate::encryption::EncryptionProvider>>,
retention: RetentionEffect,
) -> crate::Result<()> {
let prior = self.latest_version();
let seqno = seqno.max(prior.seqno);
let mut next_version = f(&prior)?;
debug_assert!(
next_version.version.id() > prior.version.id(),
"an install must advance the version id ({} -> {})",
prior.version.id(),
next_version.version.id(),
);
debug_assert!(
next_version
.version
.referenced_dicts()
.iter()
.all(|id| next_version.version.dicts().contains(id)),
"every dictionary a file references is registered ({:?} of {:?})",
next_version.version.referenced_dicts(),
next_version.version.dicts(),
);
next_version.seqno = seqno;
log::trace!("Next version seqno={}", next_version.seqno);
let floor = match retention {
RetentionEffect::Keep | RetentionEffect::GcBelow(0) => None,
RetentionEffect::GcBelow(watermark) => Some((watermark - 1).min(seqno)),
RetentionEffect::DropsData => Some(seqno),
};
if let Some(floor) = floor {
next_version.version.set_retention_floor(floor);
}
self.persist_change(
tree_path,
&prior.version,
&next_version.version,
fs,
runtime,
encryption,
)?;
self.append_version(next_version);
let next_visible = seqno.saturating_add(1).min(MAX_SEQNO);
visible_seqno.fetch_max(next_visible);
Ok(())
}
fn persist_change(
&mut self,
tree_path: &Path,
prior: &Version,
next: &Version,
fs: &dyn Fs,
runtime: Arc<crate::runtime_config::RuntimeConfig>,
encryption: Option<Arc<dyn crate::encryption::EncryptionProvider>>,
) -> crate::Result<()> {
let log_path = tree_path.join(format!("edits-{}", self.snapshot_id));
let log_size = if let Some(n) = self.log_bytes {
n
} else {
let n = edit_log::log_size(fs, &log_path)?;
self.log_bytes = Some(n);
n
};
if log_size < self.log_rotate_bytes {
let edit = next.diff(prior)?;
match edit_log::append_edit(
fs,
&log_path,
&edit,
&mut self.edit_scratch,
self.sync_mode,
) {
Ok(Some(appended)) => {
self.log_bytes = Some(log_size + appended);
return Ok(());
}
Ok(None) => {}
Err(e) => {
self.log_bytes = None;
return Err(e);
}
}
}
let old_snapshot = self.snapshot_id;
let new_log_bytes = persist_version(
tree_path,
next,
&self.comparator_name,
fs,
runtime,
encryption,
self.sync_mode,
)?;
self.snapshot_id = next.id();
self.log_bytes = Some(new_log_bytes);
if let Err(e) = remove_if_present(fs, &log_path) {
log::warn!(
"rotation: failed to remove old edit log {}: {e}",
log_path.display()
);
}
if old_snapshot != self.snapshot_id {
let old_path = tree_path.join(format!("v{old_snapshot}"));
if let Err(e) = remove_if_present(fs, &old_path) {
log::warn!(
"rotation: failed to remove old snapshot {}: {e}",
old_path.display()
);
}
}
Ok(())
}
pub fn append_version(&mut self, version: SuperVersion) {
#[cfg(feature = "std")]
self.latest.store(Arc::new(version.clone()));
self.current = version;
}
pub fn replace_latest_version(&mut self, version: SuperVersion) {
self.append_version(version);
}
#[cfg(feature = "std")]
#[must_use]
pub(crate) fn latest_handle(&self) -> Arc<ArcSwap<SuperVersion>> {
Arc::clone(&self.latest)
}
pub fn latest_version(&self) -> SuperVersion {
self.latest_version_ref().clone()
}
pub fn latest_version_ref(&self) -> &SuperVersion {
&self.current
}
#[must_use]
pub fn registered_dicts(&self) -> Vec<crate::file::DictId> {
self.current.version.dicts().to_vec()
}
pub fn get_version_for_snapshot(&self, seqno: SeqNo) -> crate::Result<SuperVersion> {
self.current.check_serves(seqno)?;
Ok(self.current.clone())
}
}
impl SuperVersion {
#[inline]
pub(crate) fn check_serves(&self, seqno: SeqNo) -> crate::Result<()> {
let floor = self.version.retention_floor();
if seqno > floor || seqno == 0 {
Ok(())
} else {
Err(below_retention(seqno, floor))
}
}
}
#[cold]
#[inline(never)]
fn below_retention(requested: SeqNo, floor: SeqNo) -> crate::Error {
crate::Error::SnapshotBelowRetention {
requested,
oldest_retained: floor,
}
}
#[cfg(test)]
mod tests;