OXYGENHUB
# 519c7c061bcb oxygen / crates / oxy-core / src / repo / gc.rs
6.6 KiB · Rust · last changed Store large inserts as text blobs (change format 2) 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; receipts
6
//! live as long as the changes of their state. Operation
7
//! objects are always kept so the log stays walkable, but an operation older
8
//! than the retention window no longer keeps its states alive, so undoing
9
//! that far back may fail once its objects are gone.
10
11
use super::*;
12
use crate::store::{PackReport, PruneReport};
13
use std::collections::HashSet;
14
15
#[derive(Debug, Clone, Default, PartialEq, Eq)]
16
pub struct GcReport {
17
    pub packed: PackReport,
18
    pub pruned: PruneReport,
19
    pub reachable: usize,
20
    /// Packs folded into one because there were more than `MAX_PACKS`.
21
    pub merged_packs: usize,
22
}
23
24
impl Repo {
25
    /// Pack loose objects and prune unreachable ones older than the grace
26
    /// period (`now` ignores the grace period).
27
    pub fn gc(&self, now: bool) -> Result<GcReport> {
28
        let packed = self.store.pack_loose()?;
29
        let keep = self.reachable()?;
30
        let grace = if now {
31
            std::time::Duration::ZERO
32
        } else {
33
            std::time::Duration::from_secs(self.config.gc_grace_days * 86_400)
34
        };
35
        let pruned = self.store.prune(&keep, grace)?;
36
        let merged_packs = self.store.merge_packs()?;
37
        Ok(GcReport {
38
            packed,
39
            pruned,
40
            reachable: keep.len(),
41
            merged_packs,
42
        })
43
    }
44
45
    /// Every object that must survive garbage collection.
46
    pub fn reachable(&self) -> Result<HashSet<ObjectRef>> {
47
        let mut keep: HashSet<ObjectRef> = HashSet::new();
48
        let mut states: Vec<ObjectRef> = Vec::new();
49
        let mut changes: Vec<ChangeHash> = Vec::new();
50
        let mut attests: Vec<ObjectRef> = Vec::new();
51
        let mut policies: Vec<ObjectRef> = Vec::new();
52
53
        // Operations: always kept; views within retention are roots.
54
        let cutoff = Hlc::now()
55
            .wall_ms
56
            .saturating_sub(self.config.op_retention_days * 86_400_000);
57
        for (i, (r, op)) in self.op_log()?.into_iter().enumerate() {
58
            keep.insert(r);
59
            if i == 0 || op.hlc.wall_ms >= cutoff {
60
                states.extend(op.view.bookmarks.values().copied());
61
                states.extend(op.view.remotes.values().flat_map(|b| b.values().copied()));
62
                changes.extend(op.view.workspaces.values().copied());
63
                policies.extend(op.view.policy);
64
            }
65
        }
66
        // Checkpoints: the object, its payload blob, and the blob's chunks.
67
        for r in self.checkpoint_refs()? {
68
            if let Ok(bytes) = self.store.get_typed(&r, ObjectType::Checkpoint) {
69
                keep.insert(r);
70
                let cp = crate::objects::Checkpoint::decode(&bytes)?;
71
                keep.insert(cp.payload);
72
                if let Ok(b) = self.store.get_typed(&cp.payload, ObjectType::Blob) {
73
                    keep.extend(Blob::decode(&b)?.chunks);
74
                }
75
                // A version 2 payload's graph blobs and their chunks.
76
                for g in self.checkpoint_graph_blobs(&cp).unwrap_or_default() {
77
                    keep.insert(g);
78
                    if let Ok(b) = self.store.get_typed(&g, ObjectType::Blob) {
79
                        keep.extend(Blob::decode(&b)?.chunks);
80
                    }
81
                }
82
            }
83
        }
84
        // Tags: their states and attestations.
85
        let view_now = self.view()?;
86
        states.extend(view_now.tags.values().copied());
87
        for name in view_now.tags.keys() {
88
            if let Some((r, _)) = self.tag_attest(name)? {
89
                attests.push(r);
90
            }
91
        }
92
        // Reference attestations.
93
        for name in self.view()?.bookmarks.keys() {
94
            if let Some((r, _)) = self.latest_ref_attest(name)? {
95
                attests.push(r);
96
            }
97
        }
98
        // A historical view may name objects pruned under an earlier, shorter
99
        // retention; there is nothing to keep for those.
100
        for st in states {
101
            if keep.insert(st) {
102
                match self.store.get_state(&st) {
103
                    Ok(s) => changes.extend(s.heads),
104
                    Err(StoreError::NotFound(_)) => continue,
105
                    Err(e) => return Err(e.into()),
106
                }
107
            }
108
        }
109
        for p in policies {
110
            if keep.insert(p) {
111
                let bytes = match self.store.get_typed(&p, ObjectType::Policy) {
112
                    Ok(b) => b,
113
                    Err(StoreError::NotFound(_)) => continue,
114
                    Err(e) => return Err(e.into()),
115
                };
116
                let policy = crate::policy::Policy::decode(&bytes)?;
117
                for id in &policy.identities {
118
                    keep.insert(id.hash());
119
                }
120
                for sig in self.store.signatures(&p)? {
121
                    keep.insert(sig.key);
122
                }
123
            }
124
        }
125
        // Changes: their closure, identities, blobs, chunks, seals.
126
        let mut stack = changes;
127
        while let Some(h) = stack.pop() {
128
            if !keep.insert(h) {
129
                continue;
130
            }
131
            // Beyond the shallow boundary, or pruned under an earlier
132
            // retention, there is nothing to keep.
133
            let c = match self.store.get_change_parts(&h) {
134
                Ok((c, texts)) => {
135
                    keep.extend(texts);
136
                    c
137
                }
138
                Err(StoreError::NotFound(_)) => continue,
139
                Err(e) => return Err(e.into()),
140
            };
141
            stack.extend(c.deps.iter().copied());
142
            if !c.author.is_zero() {
143
                keep.insert(c.author);
144
            }
145
            for sig in self.store.signatures(&h)? {
146
                keep.insert(sig.key);
147
            }
148
            for e in &c.edits {
149
                if let Edit::Replace { old, new, .. } = e {
150
                    for b in [old, new].into_iter().flatten() {
151
                        if keep.insert(*b) {
152
                            if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
153
                                keep.extend(Blob::decode(&bytes)?.chunks);
154
                            }
155
                        }
156
                    }
157
                }
158
            }
159
            attests.extend(self.store.seals_for(&h)?);
160
        }
161
        self.reachable_receipts(&mut keep)?;
162
        for a in attests {
163
            if keep.insert(a) {
164
                for sig in self.store.signatures(&a)? {
165
                    keep.insert(sig.key);
166
                }
167
            }
168
        }
169
        Ok(keep)
170
    }
171
}