OXYGENHUB
# f7510b63cceb oxygen / crates / oxy-core / src / repo / gc.rs
5.9 KiB · Rust · last changed Checkpoint payload version 2: large file graphs as shared blobs named from an index, read in parallel (loose and packed) and decoded in parallel; version 1 still read; gc, fsck and shallow-clone bundles carry the graph blobs (spec 11, Draft 0.8j) 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
    /// Packs folded into one because there were more than `MAX_PACKS`.
20
    pub merged_packs: usize,
21
}
22
23
impl Repo {
24
    /// Pack loose objects and prune unreachable ones older than the grace
25
    /// period (`now` ignores the grace period).
26
    pub fn gc(&self, now: bool) -> Result<GcReport> {
27
        let packed = self.store.pack_loose()?;
28
        let keep = self.reachable()?;
29
        let grace = if now {
30
            std::time::Duration::ZERO
31
        } else {
32
            std::time::Duration::from_secs(self.config.gc_grace_days * 86_400)
33
        };
34
        let pruned = self.store.prune(&keep, grace)?;
35
        let merged_packs = self.store.merge_packs()?;
36
        Ok(GcReport {
37
            packed,
38
            pruned,
39
            reachable: keep.len(),
40
            merged_packs,
41
        })
42
    }
43
44
    /// Every object that must survive garbage collection.
45
    pub fn reachable(&self) -> Result<HashSet<ObjectRef>> {
46
        let mut keep: HashSet<ObjectRef> = HashSet::new();
47
        let mut states: Vec<ObjectRef> = Vec::new();
48
        let mut changes: Vec<ChangeHash> = Vec::new();
49
        let mut attests: Vec<ObjectRef> = Vec::new();
50
        let mut policies: Vec<ObjectRef> = Vec::new();
51
52
        // Operations: always kept; views within retention are roots.
53
        let cutoff = Hlc::now()
54
            .wall_ms
55
            .saturating_sub(self.config.op_retention_days * 86_400_000);
56
        for (i, (r, op)) in self.op_log()?.into_iter().enumerate() {
57
            keep.insert(r);
58
            if i == 0 || op.hlc.wall_ms >= cutoff {
59
                states.extend(op.view.bookmarks.values().copied());
60
                states.extend(op.view.remotes.values().flat_map(|b| b.values().copied()));
61
                changes.extend(op.view.workspaces.values().copied());
62
                policies.extend(op.view.policy);
63
            }
64
        }
65
        // Checkpoints: the object, its payload blob, and the blob's chunks.
66
        for r in self.checkpoint_refs()? {
67
            if let Ok(bytes) = self.store.get_typed(&r, ObjectType::Checkpoint) {
68
                keep.insert(r);
69
                let cp = crate::objects::Checkpoint::decode(&bytes)?;
70
                keep.insert(cp.payload);
71
                if let Ok(b) = self.store.get_typed(&cp.payload, ObjectType::Blob) {
72
                    keep.extend(Blob::decode(&b)?.chunks);
73
                }
74
                // A version 2 payload's graph blobs and their chunks.
75
                for g in self.checkpoint_graph_blobs(&cp).unwrap_or_default() {
76
                    keep.insert(g);
77
                    if let Ok(b) = self.store.get_typed(&g, ObjectType::Blob) {
78
                        keep.extend(Blob::decode(&b)?.chunks);
79
                    }
80
                }
81
            }
82
        }
83
        // Tags: their states and attestations.
84
        let view_now = self.view()?;
85
        states.extend(view_now.tags.values().copied());
86
        for name in view_now.tags.keys() {
87
            if let Some((r, _)) = self.tag_attest(name)? {
88
                attests.push(r);
89
            }
90
        }
91
        // Reference attestations.
92
        for name in self.view()?.bookmarks.keys() {
93
            if let Some((r, _)) = self.latest_ref_attest(name)? {
94
                attests.push(r);
95
            }
96
        }
97
        for st in states {
98
            if keep.insert(st) {
99
                changes.extend(self.store.get_state(&st)?.heads);
100
            }
101
        }
102
        for p in policies {
103
            if keep.insert(p) {
104
                let policy = crate::policy::Policy::decode(&self.store.get_typed(&p, ObjectType::Policy)?)?;
105
                for id in &policy.identities {
106
                    keep.insert(id.hash());
107
                }
108
                for sig in self.store.signatures(&p)? {
109
                    keep.insert(sig.key);
110
                }
111
            }
112
        }
113
        // Changes: their closure, identities, blobs, chunks, seals.
114
        let mut stack = changes;
115
        while let Some(h) = stack.pop() {
116
            if !keep.insert(h) {
117
                continue;
118
            }
119
            // Beyond the shallow boundary there is nothing to keep.
120
            let c = match self.store.get_change(&h) {
121
                Ok(c) => c,
122
                Err(StoreError::NotFound(_)) => continue,
123
                Err(e) => return Err(e.into()),
124
            };
125
            stack.extend(c.deps.iter().copied());
126
            if !c.author.is_zero() {
127
                keep.insert(c.author);
128
            }
129
            for sig in self.store.signatures(&h)? {
130
                keep.insert(sig.key);
131
            }
132
            for e in &c.edits {
133
                if let Edit::Replace { old, new, .. } = e {
134
                    for b in [old, new].into_iter().flatten() {
135
                        if keep.insert(*b) {
136
                            if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
137
                                keep.extend(Blob::decode(&bytes)?.chunks);
138
                            }
139
                        }
140
                    }
141
                }
142
            }
143
            attests.extend(self.store.seals_for(&h)?);
144
        }
145
        for a in attests {
146
            if keep.insert(a) {
147
                for sig in self.store.signatures(&a)? {
148
                    keep.insert(sig.key);
149
                }
150
            }
151
        }
152
        Ok(keep)
153
    }
154
}