use super::{BranchId, Commit, CommitId, NO_COMMIT, diff::DiffEntry};
use crate::{
Mapx, MapxOrd, Orphan,
basic::persistent_btree::{EMPTY_ROOT, NodeId, PersistentBTree, TreeDiff},
common::{
Colocate, InstanceId,
ende::{KeyEnDeOrdered, OrderedKeyRef, ValueEnDe},
error::{Result, VsdbError},
},
};
use serde::{Deserialize, Serialize};
use std::{
borrow::Borrow,
collections::{BinaryHeap, HashMap, HashSet},
marker::PhantomData,
time::{SystemTime, UNIX_EPOCH},
};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct BranchState {
pub(crate) name: String,
pub(crate) head: CommitId,
pub(crate) dirty_root: NodeId,
}
#[derive(Debug)]
pub struct VerMap<K, V> {
pub(crate) tree: PersistentBTree,
pub(crate) commits: MapxOrd<u64, Commit>,
pub(crate) branches: MapxOrd<u64, BranchState>,
pub(crate) branch_names: Mapx<String, u64>,
pub(crate) next_commit: Orphan<u64>,
pub(crate) next_branch: Orphan<u64>,
pub(crate) main_branch: Orphan<u64>,
pub(crate) gc_dirty: Orphan<bool>,
pub(crate) colocated: bool,
_phantom: PhantomData<(K, V)>,
}
const RECLAIM_BACKLOG: usize = 16 * 1024;
impl<K: Clone, V: Clone> Clone for VerMap<K, V> {
fn clone(&self) -> Self {
let mut tree = self.tree.clone();
tree.defer_reclaim();
let anchor = &tree.nodes;
fn copy<T>(r: Result<T>) -> T {
r.expect("vsdb: clone failed — I/O error")
}
let commits = copy(self.commits.clone_colocated(anchor));
let branches = copy(self.branches.clone_colocated(anchor));
let branch_names = copy(self.branch_names.clone_colocated(anchor));
let next_commit = copy(self.next_commit.clone_colocated(anchor));
let next_branch = copy(self.next_branch.clone_colocated(anchor));
let main_branch = copy(self.main_branch.clone_colocated(anchor));
let gc_dirty = copy(self.gc_dirty.clone_colocated(anchor));
let mut cloned = Self {
tree,
commits,
branches,
branch_names,
next_commit,
next_branch,
main_branch,
gc_dirty,
colocated: true,
_phantom: PhantomData,
};
cloned.rebuild_tree_ref_counts();
cloned
}
}
impl<K, V> Serialize for VerMap<K, V> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
crate::common::serialize_typed_handle_meta::<Self, S>(
&(
&self.tree,
&self.commits,
&self.branches,
&self.branch_names,
&self.next_commit,
&self.next_branch,
&self.main_branch,
&self.gc_dirty,
),
serializer,
)
}
}
type VerMapPayload = (
PersistentBTree,
MapxOrd<u64, Commit>,
MapxOrd<u64, BranchState>,
Mapx<String, u64>,
Orphan<u64>,
Orphan<u64>,
Orphan<u64>,
Orphan<bool>,
);
impl<'de, K, V> Deserialize<'de> for VerMap<K, V> {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let (
tree,
commits,
branches,
branch_names,
next_commit,
next_branch,
main_branch,
gc_dirty,
) = crate::common::deserialize_typed_handle_meta::<Self, VerMapPayload, D>(
deserializer,
)?;
let mut m = VerMap {
tree,
commits,
branches,
branch_names,
next_commit,
next_branch,
main_branch,
gc_dirty,
colocated: false,
_phantom: PhantomData,
};
let anchor = &m.tree.nodes;
m.colocated = [
m.commits.raw(),
m.branches.raw(),
m.branch_names.raw(),
m.next_commit.raw(),
m.next_branch.raw(),
m.main_branch.raw(),
m.gc_dirty.raw(),
]
.into_iter()
.all(|component| component.is_colocated_with(anchor));
if m.colocated {
m.tree.defer_reclaim();
}
m.repair_commit_ref_counts_if_needed()
.map_err(serde::de::Error::custom)?;
m.rebuild_tree_ref_counts();
Ok(m)
}
}
impl<K, V> Default for VerMap<K, V>
where
K: KeyEnDeOrdered,
V: ValueEnDe,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V> VerMap<K, V> {
pub fn instance_id(&self) -> InstanceId {
self.tree.instance_id()
}
pub fn namespace(&self) -> crate::common::Namespace {
self.tree.namespace()
}
fn ensure_writable(&self, operation: &'static str) -> Result<()> {
crate::common::ensure_writable(&self.namespace(), operation)
}
pub(crate) fn sync_storage(&self) {
if self.namespace().is_read_only() {
return;
}
self.tree.nodes.sync_wal();
if self.colocated {
return;
}
self.next_commit.sync_wal();
self.next_branch.sync_wal();
self.commits.sync_wal();
self.branches.sync_wal();
self.branch_names.sync_wal();
self.main_branch.sync_wal();
self.gc_dirty.sync_wal();
}
pub(crate) fn get_branch(&self, id: BranchId) -> Result<BranchState> {
self.branches
.get(&id.0)
.ok_or(VsdbError::BranchNotFound { branch_id: id.0 })
}
pub(crate) fn get_commit_inner(&self, id: CommitId) -> Result<Commit> {
self.commits
.get(&id.0)
.ok_or(VsdbError::CommitNotFound { commit_id: id.0 })
}
pub fn raw_diff_commits(
&self,
from: CommitId,
to: CommitId,
) -> Result<Vec<TreeDiff>> {
let [from_root, to_root] = self.tree.lease_roots(|| {
Ok([
self.get_commit_inner(from)?.root,
self.get_commit_inner(to)?.root,
])
})?;
#[cfg(test)]
super::test::before_diff_traversal();
Ok(self.tree.diff(from_root.root, to_root.root))
}
pub fn raw_diff_uncommitted(&self, branch: BranchId) -> Result<Vec<TreeDiff>> {
let [head_root, dirty_root] = self.tree.lease_roots(|| {
let state = self.get_branch(branch)?;
let head_root = if state.head == NO_COMMIT {
EMPTY_ROOT
} else {
self.get_commit_inner(state.head)?.root
};
Ok([head_root, state.dirty_root])
})?;
#[cfg(test)]
super::test::before_diff_traversal();
Ok(self.tree.diff(head_root.root, dirty_root.root))
}
pub(crate) fn begin_ref_update(&mut self) {
*self.gc_dirty.get_mut() = true;
self.fence(|m| m.gc_dirty.sync_wal());
}
pub(crate) fn end_ref_update(&mut self) {
*self.gc_dirty.get_mut() = false;
self.fence(|m| m.gc_dirty.sync_wal());
}
#[inline]
pub(crate) fn fence(&self, sync: impl FnOnce(&Self)) {
if !self.colocated {
sync(self);
}
}
pub(crate) fn settle(&mut self) {
if self.colocated && !self.namespace().is_read_only() {
self.tree.nodes.sync_wal();
self.tree.register_deferred_reclaims();
}
}
fn settle_if_backlogged(&mut self) {
if self.tree.deferred_reclaims() >= RECLAIM_BACKLOG {
self.settle();
}
}
}
impl<K, V> VerMap<K, V>
where
K: KeyEnDeOrdered,
V: ValueEnDe,
{
pub fn new() -> Self {
Self::new_with_main("main")
}
pub fn new_in(ns: &crate::common::Namespace) -> Self {
ns.scope(Self::new)
}
pub fn save_meta(&self) -> Result<InstanceId> {
let id = self.instance_id();
crate::common::save_instance_meta(id, self)?;
Ok(id)
}
pub fn from_meta(instance_id: impl Into<InstanceId>) -> Result<Self> {
let id = instance_id.into();
crate::common::load_instance_meta_checked(id, Self::instance_id)
}
pub fn new_with_main(name: &str) -> Self {
let mut tree = PersistentBTree::new();
tree.defer_reclaim();
let anchor = &tree.nodes;
let mut branches: MapxOrd<u64, BranchState> = Colocate::new_colocated(anchor);
let mut branch_names: Mapx<String, u64> = Colocate::new_colocated(anchor);
let initial_id = BranchId(1);
let main = BranchState {
name: name.into(),
head: NO_COMMIT,
dirty_root: EMPTY_ROOT,
};
branches.insert(&initial_id.0, &main);
branch_names.insert(&name.to_string(), &initial_id.0);
let commits = Colocate::new_colocated(anchor);
let next_commit = Orphan::new_colocated(anchor, NO_COMMIT.0 + 1);
let next_branch = Orphan::new_colocated(anchor, initial_id.0 + 1);
let main_branch = Orphan::new_colocated(anchor, initial_id.0);
let gc_dirty = Orphan::new_colocated(anchor, false);
let map = Self {
tree,
commits,
branches,
branch_names,
next_commit,
next_branch,
main_branch,
gc_dirty,
colocated: true,
_phantom: PhantomData,
};
map.sync_storage();
map
}
#[cfg(test)]
pub(crate) fn with_legacy_layout() -> Self {
let tree = PersistentBTree::new();
let anchor = tree.nodes.clone();
let commits = loop {
let c: MapxOrd<u64, Commit> = MapxOrd::new();
if !c.raw().is_colocated_with(&anchor) {
break c;
}
};
let mut branches: MapxOrd<u64, BranchState> = MapxOrd::new();
let mut branch_names: Mapx<String, u64> = Mapx::new();
branches.insert(
&1,
&BranchState {
name: "main".into(),
head: NO_COMMIT,
dirty_root: EMPTY_ROOT,
},
);
branch_names.insert(&"main".to_string(), &1);
let map = Self {
tree,
commits,
branches,
branch_names,
next_commit: Orphan::new(1),
next_branch: Orphan::new(2),
main_branch: Orphan::new(1),
gc_dirty: Orphan::new(false),
colocated: false,
_phantom: PhantomData,
};
map.sync_storage();
map
}
fn branch_name_exists(&self, name: &str) -> bool {
self.branches.iter().any(|(_, state)| state.name == name)
}
pub fn main_branch(&self) -> BranchId {
BranchId(self.main_branch.get_value())
}
pub fn set_main_branch(&mut self, branch: BranchId) -> Result<()> {
self.ensure_writable("main branch update")?;
self.get_branch(branch)?;
*self.main_branch.get_mut() = branch.0;
self.fence(|m| m.main_branch.sync_wal());
self.settle();
Ok(())
}
pub fn create_branch(
&mut self,
name: &str,
source_branch: BranchId,
) -> Result<BranchId> {
self.ensure_writable("branch creation")?;
if self.branch_name_exists(name) {
return Err(VsdbError::BranchAlreadyExists {
name: name.to_string(),
});
}
let src = self.get_branch(source_branch)?;
self.begin_ref_update();
let id = BranchId(self.next_branch.get_value());
*self.next_branch.get_mut() = id.0 + 1;
self.fence(|m| m.next_branch.sync_wal());
let state = BranchState {
name: name.into(),
head: src.head,
dirty_root: src.dirty_root,
};
self.branches.insert(&id.0, &state);
self.branch_names.insert(&name.to_string(), &id.0);
self.increment_ref(src.head);
self.tree.acquire_node(src.dirty_root);
self.fence(|m| {
m.commits.sync_wal();
m.branches.sync_wal();
m.branch_names.sync_wal();
});
self.end_ref_update();
self.settle();
Ok(id)
}
pub fn delete_branch(&mut self, branch: BranchId) -> Result<()> {
self.ensure_writable("branch deletion")?;
if branch == self.main_branch() {
return Err(VsdbError::CannotDeleteMainBranch);
}
let state = self.get_branch(branch)?;
let dead_head = state.head;
let dead_dirty = state.dirty_root;
self.begin_ref_update();
self.branch_names.remove(&state.name);
self.branches.remove(&branch.0);
self.fence(|m| {
m.branches.sync_wal();
m.branch_names.sync_wal();
});
self.tree.release_node(dead_dirty);
self.decrement_ref(dead_head);
self.end_ref_update();
self.settle();
Ok(())
}
pub fn list_branches(&self) -> Vec<(BranchId, String)> {
self.branches
.iter()
.map(|(id, s)| (BranchId(id), s.name))
.collect()
}
pub fn branch_id(&self, name: &str) -> Option<BranchId> {
if self.namespace().is_read_only() {
self.branches
.iter()
.find_map(|(id, state)| (state.name == name).then_some(BranchId(id)))
} else {
self.branch_names.get(&name.to_string()).map(BranchId)
}
}
pub fn branch_name(&self, branch: BranchId) -> Option<String> {
self.branches.get(&branch.0).map(|s| s.name)
}
pub fn has_uncommitted(&self, branch: BranchId) -> Result<bool> {
let state = self.get_branch(branch)?;
if state.head == NO_COMMIT {
Ok(state.dirty_root != EMPTY_ROOT)
} else {
let head_root = self.get_commit_inner(state.head)?.root;
Ok(state.dirty_root != head_root)
}
}
pub fn insert(&mut self, branch: BranchId, key: &K, value: &V) -> Result<()> {
self.ensure_writable("versioned map insert")?;
let mut state = self.get_branch(branch)?;
let old_root = state.dirty_root;
state.dirty_root = self.tree.insert(old_root, &key.to_bytes(), &value.encode());
self.fence(|m| m.tree.nodes.sync_wal());
self.tree.acquire_node(state.dirty_root);
self.branches.insert(&branch.0, &state);
self.fence(|m| m.branches.sync_wal());
self.tree.release_node(old_root);
self.settle_if_backlogged();
Ok(())
}
pub fn remove<Q>(&mut self, branch: BranchId, key: &Q) -> Result<()>
where
K: Borrow<Q>,
Q: OrderedKeyRef + ?Sized,
{
self.ensure_writable("versioned map remove")?;
let mut state = self.get_branch(branch)?;
let old_root = state.dirty_root;
state.dirty_root = self.tree.remove(old_root, &key.ordered_key_bytes());
if state.dirty_root == old_root {
return Ok(());
}
self.fence(|m| m.tree.nodes.sync_wal());
self.tree.acquire_node(state.dirty_root);
self.branches.insert(&branch.0, &state);
self.fence(|m| m.branches.sync_wal());
self.tree.release_node(old_root);
self.settle_if_backlogged();
Ok(())
}
pub fn commit(&mut self, branch: BranchId) -> Result<CommitId> {
self.ensure_writable("versioned map commit")?;
let state = self.get_branch(branch)?;
self.begin_ref_update();
let id = CommitId(self.next_commit.get_value());
*self.next_commit.get_mut() = id.0 + 1;
self.fence(|m| m.next_commit.sync_wal());
let parents = if state.head == NO_COMMIT {
vec![]
} else {
vec![state.head]
};
let commit = Commit {
id,
root: state.dirty_root,
parents,
timestamp_us: now_us(),
ref_count: 1,
};
self.commits.insert(&id.0, &commit);
self.fence(|m| m.commits.sync_wal());
self.tree.acquire_node(state.dirty_root);
let new_state = BranchState { head: id, ..state };
self.branches.insert(&branch.0, &new_state);
self.fence(|m| m.branches.sync_wal());
self.end_ref_update();
self.settle();
Ok(id)
}
pub fn discard(&mut self, branch: BranchId) -> Result<()> {
self.ensure_writable("working-state discard")?;
let state = self.get_branch(branch)?;
let old_dirty = state.dirty_root;
let root = if state.head == NO_COMMIT {
EMPTY_ROOT
} else {
self.get_commit_inner(state.head)?.root
};
let new_state = BranchState {
dirty_root: root,
..state
};
self.tree.acquire_node(root);
self.branches.insert(&branch.0, &new_state);
self.fence(|m| m.branches.sync_wal());
self.tree.release_node(old_dirty);
self.settle_if_backlogged();
Ok(())
}
pub fn rollback_to(&mut self, branch: BranchId, target: CommitId) -> Result<()> {
self.ensure_writable("branch rollback")?;
let state = self.get_branch(branch)?;
let _ = self.get_commit_inner(target)?;
if state.head == NO_COMMIT {
return Err(VsdbError::NotAncestor {
commit_id: target.0,
branch_id: branch.0,
});
}
if self.has_uncommitted(branch)? {
return Err(VsdbError::UncommittedChanges {
branch_id: branch.0,
});
}
if target == state.head {
return Ok(());
}
{
let mut queue = vec![state.head];
let mut visited = HashSet::new();
let mut found = false;
while let Some(cur) = queue.pop() {
if cur == NO_COMMIT || !visited.insert(cur) {
continue;
}
if cur == target {
found = true;
break;
}
if let Some(c) = self.commits.get(&cur.0) {
queue.extend_from_slice(&c.parents);
}
}
if !found {
return Err(VsdbError::NotAncestor {
commit_id: target.0,
branch_id: branch.0,
});
}
}
self.begin_ref_update();
let commit = self.get_commit_inner(target)?;
let old_head = state.head;
let old_dirty = state.dirty_root;
let new_state = BranchState {
name: state.name,
head: target,
dirty_root: commit.root,
};
self.branches.insert(&branch.0, &new_state);
self.fence(|m| m.branches.sync_wal());
self.tree.acquire_node(commit.root);
self.tree.release_node(old_dirty);
self.increment_ref(target);
self.decrement_ref(old_head);
self.end_ref_update();
self.settle();
Ok(())
}
pub fn merge(&mut self, source: BranchId, target: BranchId) -> Result<CommitId> {
self.ensure_writable("branch merge")?;
if source == target {
return Err(VsdbError::SelfMerge {
branch_id: source.0,
});
}
if self.has_uncommitted(source)? {
return Err(VsdbError::UncommittedChanges {
branch_id: source.0,
});
}
if self.has_uncommitted(target)? {
return Err(VsdbError::UncommittedChanges {
branch_id: target.0,
});
}
let src = self.get_branch(source)?;
let tgt = self.get_branch(target)?;
if src.head == NO_COMMIT {
return Err(VsdbError::NoCommits {
branch_id: source.0,
});
}
if src.head == tgt.head {
return Ok(tgt.head);
}
self.begin_ref_update();
if tgt.head == NO_COMMIT {
let src_commit = self.get_commit_inner(src.head)?;
let new_state = BranchState {
head: src.head,
dirty_root: src_commit.root,
..tgt
};
self.branches.insert(&target.0, &new_state);
self.fence(|m| m.branches.sync_wal());
self.increment_ref(src.head);
self.tree.acquire_node(src_commit.root);
self.tree.release_node(tgt.dirty_root);
self.fence(|m| m.commits.sync_wal());
self.end_ref_update();
self.settle();
return Ok(src.head);
}
let src_commit = self.get_commit_inner(src.head)?;
let tgt_commit = self.get_commit_inner(tgt.head)?;
let ancestor_roots: Vec<NodeId> = self
.find_merge_bases(src.head, tgt.head)
.into_iter()
.map(|aid| self.get_commit_inner(aid).map(|c| c.root))
.collect::<Result<_>>()?;
let ancestor_roots = if ancestor_roots.is_empty() {
vec![EMPTY_ROOT]
} else {
ancestor_roots
};
let merged_root =
self.tree
.merge(&ancestor_roots, src_commit.root, tgt_commit.root);
self.fence(|m| m.tree.nodes.sync_wal());
self.tree.acquire_node(merged_root); self.tree.acquire_node(merged_root);
let id = CommitId(self.next_commit.get_value());
*self.next_commit.get_mut() = id.0 + 1;
self.fence(|m| m.next_commit.sync_wal());
let commit = Commit {
id,
root: merged_root,
parents: vec![tgt.head, src.head],
timestamp_us: now_us(),
ref_count: 1,
};
self.commits.insert(&id.0, &commit);
self.increment_ref(src.head);
self.fence(|m| m.commits.sync_wal());
let new_state = BranchState {
head: id,
dirty_root: merged_root,
..tgt
};
self.branches.insert(&target.0, &new_state);
self.fence(|m| m.branches.sync_wal());
self.tree.release_node(tgt.dirty_root);
self.end_ref_update();
self.settle();
Ok(id)
}
fn find_merge_bases(&self, a: CommitId, b: CommitId) -> Vec<CommitId> {
const FROM_A: u8 = 0b001;
const FROM_B: u8 = 0b010;
const BOTH: u8 = FROM_A | FROM_B;
const STALE: u8 = 0b100;
if self.commits.get(&a.0).is_none() || self.commits.get(&b.0).is_none() {
return vec![];
}
fn mark(
flags: &mut HashMap<CommitId, u8>,
heap: &mut BinaryHeap<CommitId>,
id: CommitId,
flag: u8,
) {
if id == NO_COMMIT {
return;
}
let slot = flags.entry(id).or_insert(0);
if *slot == 0 {
heap.push(id);
}
*slot |= flag;
}
let mut flags: HashMap<CommitId, u8> = HashMap::new();
let mut heap: BinaryHeap<CommitId> = BinaryHeap::new();
let mut bases = Vec::new();
mark(&mut flags, &mut heap, a, FROM_A);
mark(&mut flags, &mut heap, b, FROM_B);
while heap.iter().any(|id| flags[id] & STALE == 0) {
let id = heap.pop().unwrap();
let mut f = flags[&id];
if f & BOTH == BOTH {
if f & STALE == 0 {
bases.push(id);
}
f |= STALE;
}
if let Some(c) = self.commits.get(&id.0) {
for &parent in &c.parents {
mark(&mut flags, &mut heap, parent, f);
}
}
}
bases
}
fn find_common_ancestor(&self, a: CommitId, b: CommitId) -> Option<CommitId> {
self.find_merge_bases(a, b).into_iter().max()
}
pub fn fork_point(&self, a: CommitId, b: CommitId) -> Option<CommitId> {
self.find_common_ancestor(a, b)
}
pub fn commit_distance(&self, from: CommitId, ancestor: CommitId) -> Option<u64> {
self.commits.get(&from.0)?;
self.commits.get(&ancestor.0)?;
let mut cur = from;
let mut count = 0u64;
while cur != ancestor {
if cur == NO_COMMIT {
return None;
}
let c = self.commits.get(&cur.0)?;
cur = c.parents.first().copied().unwrap_or(NO_COMMIT);
count += 1;
}
Some(count)
}
pub fn get_commit(&self, commit_id: CommitId) -> Option<Commit> {
self.commits.get(&commit_id.0)
}
pub fn head_commit(&self, branch: BranchId) -> Result<Option<Commit>> {
let state = self.get_branch(branch)?;
if state.head == NO_COMMIT {
Ok(None)
} else {
Ok(self.commits.get(&state.head.0))
}
}
pub fn log(&self, branch: BranchId) -> Result<Vec<Commit>> {
let state = self.get_branch(branch)?;
let mut result = Vec::new();
let mut cur = state.head;
while cur != NO_COMMIT {
if let Some(c) = self.commits.get(&cur.0) {
cur = c.parents.first().copied().unwrap_or(NO_COMMIT);
result.push(c);
} else {
break;
}
}
Ok(result)
}
pub fn diff_commits(
&self,
from: CommitId,
to: CommitId,
) -> Result<Vec<DiffEntry<K, V>>> {
Ok(self
.raw_diff_commits(from, to)?
.into_iter()
.map(DiffEntry::decode)
.collect())
}
pub fn diff_uncommitted(&self, branch: BranchId) -> Result<Vec<DiffEntry<K, V>>> {
Ok(self
.raw_diff_uncommitted(branch)?
.into_iter()
.map(DiffEntry::decode)
.collect())
}
pub fn gc(&mut self) -> Result<()> {
if self.namespace().is_read_only() {
return Ok(());
}
self.validate_commit_graph()?;
if self.gc_dirty.get_value()
|| self.commits.iter().any(|(_, c)| c.ref_count == 0)
{
self.rebuild_ref_counts()?;
}
self.sync_storage();
self.tree.register_deferred_reclaims();
let mut live_roots: Vec<NodeId> =
self.commits.iter().map(|(_, c)| c.root).collect();
for (_, s) in self.branches.iter() {
if s.dirty_root != EMPTY_ROOT {
live_roots.push(s.dirty_root);
}
}
self.tree.gc(&live_roots);
Ok(())
}
fn increment_ref(&mut self, commit_id: CommitId) {
if commit_id == NO_COMMIT {
return;
}
if let Some(mut c) = self.commits.get(&commit_id.0) {
c.ref_count += 1;
self.commits.insert(&commit_id.0, &c);
}
}
fn decrement_ref(&mut self, commit_id: CommitId) {
if commit_id == NO_COMMIT {
return;
}
let already_dirty = self.gc_dirty.get_value();
if !already_dirty {
self.begin_ref_update();
}
let mut dead_roots = Vec::new();
let mut work = vec![commit_id];
while let Some(id) = work.pop() {
if id == NO_COMMIT {
continue;
}
let Some(mut c) = self.commits.get(&id.0) else {
continue; };
c.ref_count = c.ref_count.saturating_sub(1);
if c.ref_count == 0 {
let parents = c.parents.clone();
dead_roots.push(c.root);
self.commits.remove(&id.0);
work.extend(parents);
} else {
self.commits.insert(&id.0, &c);
}
}
self.fence(|m| m.commits.sync_wal());
for root in dead_roots {
self.tree.release_node(root);
}
if !already_dirty {
self.end_ref_update();
}
}
}
fn now_us() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or(0)
}