OXYGENHUB
# e8b4e156f2fa oxygen / crates / oxy-core / src / repo / gc.rs
4.2 KiB · Rust · last changed Garbage collection: operation-log retention, pruning with grace period, pack rewriting; oxy gc --now 1 month ago source blame history raw
1
//! Garbage collection (spec §21): pack loose objects, then delete objects
2
//! nothing reachable refers to.
3
//!
4
//! Roots: the current view, the views of every operation newer than
5
//! `core.op-retention`, reference attestations, and the policy. Operation
6
//! objects are always kept so the log stays walkable, but an operation older
7
//! than the retention window no longer keeps its states alive, so undoing
8
//! that far back may fail once its objects are gone.
9
10
use super::*;
11
use crate::store::{PackReport, PruneReport};
12
use std::collections::HashSet;
13
14
#[derive(Debug, Clone, Default, PartialEq, Eq)]
15
pub struct GcReport {
16
    pub packed: PackReport,
17
    pub pruned: PruneReport,
18
    pub reachable: usize,
19
}
20
21
impl Repo {
22
    /// Pack loose objects and prune unreachable ones older than the grace
23
    /// period (`now` ignores the grace period).
24
    pub fn gc(&self, now: bool) -> Result<GcReport> {
25
        let packed = self.store.pack_loose()?;
26
        let keep = self.reachable()?;
27
        let grace = if now { std::time::Duration::ZERO } else { std::time::Duration::from_secs(self.config.gc_grace_days * 86_400) };
28
        let pruned = self.store.prune(&keep, grace)?;
29
        Ok(GcReport { packed, pruned, reachable: keep.len() })
30
    }
31
32
    /// Every object that must survive garbage collection.
33
    pub fn reachable(&self) -> Result<HashSet<ObjectRef>> {
34
        let mut keep: HashSet<ObjectRef> = HashSet::new();
35
        let mut states: Vec<ObjectRef> = Vec::new();
36
        let mut changes: Vec<ChangeHash> = Vec::new();
37
        let mut attests: Vec<ObjectRef> = Vec::new();
38
        let mut policies: Vec<ObjectRef> = Vec::new();
39
40
        // Operations: always kept; views within retention are roots.
41
        let cutoff = Hlc::now().wall_ms.saturating_sub(self.config.op_retention_days * 86_400_000);
42
        for (i, (r, op)) in self.op_log()?.into_iter().enumerate() {
43
            keep.insert(r);
44
            if i == 0 || op.hlc.wall_ms >= cutoff {
45
                states.extend(op.view.bookmarks.values().copied());
46
                states.extend(op.view.remotes.values().flat_map(|b| b.values().copied()));
47
                changes.extend(op.view.workspaces.values().copied());
48
                policies.extend(op.view.policy);
49
            }
50
        }
51
        // Reference attestations.
52
        for name in self.view()?.bookmarks.keys() {
53
            if let Some((r, _)) = self.latest_ref_attest(name)? {
54
                attests.push(r);
55
            }
56
        }
57
        for st in states {
58
            if keep.insert(st) {
59
                changes.extend(self.store.get_state(&st)?.heads);
60
            }
61
        }
62
        for p in policies {
63
            if keep.insert(p) {
64
                let policy = crate::policy::Policy::decode(&self.store.get_typed(&p, ObjectType::Policy)?)?;
65
                for id in &policy.identities {
66
                    keep.insert(id.hash());
67
                }
68
                for sig in self.store.signatures(&p)? {
69
                    keep.insert(sig.key);
70
                }
71
            }
72
        }
73
        // Changes: their closure, identities, blobs, chunks, seals.
74
        let mut stack = changes;
75
        while let Some(h) = stack.pop() {
76
            if !keep.insert(h) {
77
                continue;
78
            }
79
            let c = self.store.get_change(&h)?;
80
            stack.extend(c.deps.iter().copied());
81
            if !c.author.is_zero() {
82
                keep.insert(c.author);
83
            }
84
            for sig in self.store.signatures(&h)? {
85
                keep.insert(sig.key);
86
            }
87
            for e in &c.edits {
88
                if let Edit::Replace { old, new, .. } = e {
89
                    for b in [old, new].into_iter().flatten() {
90
                        if keep.insert(*b) {
91
                            if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
92
                                keep.extend(Blob::decode(&bytes)?.chunks);
93
                            }
94
                        }
95
                    }
96
                }
97
            }
98
            attests.extend(self.store.seals_for(&h)?);
99
        }
100
        for a in attests {
101
            if keep.insert(a) {
102
                for sig in self.store.signatures(&a)? {
103
                    keep.insert(sig.key);
104
                }
105
            }
106
        }
107
        Ok(keep)
108
    }
109
}