OXYGENHUB
◆ v0.3.1 oxygen / crates / oxy-core / src / repo / gc.rs
6.7 KiB · Rust · last changed gc repacks everything into one pack, compressed harder 1 month ago source blame history raw
da652d63 Andy McCall ↶ 1
//! Garbage collection (spec §21): delete objects
ba160f60 Andy McCall ↶ 2
//! nothing reachable refers to.
3
//!
4
//! Roots: the current view, the views of every operation newer than
fc1a466a Andy McCall ↶ 5
//! `core.op-retention`, reference attestations, and the policy; receipts
6
//! live as long as the changes of their state. Operation
ba160f60 Andy McCall ↶ 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,
da652d63 Andy McCall ↶ 20
    /// Packs folded into the one a full repack writes.
135212de Andy McCall ↶ 21
    pub merged_packs: usize,
ba160f60 Andy McCall ↶ 22
}
23
24
impl Repo {
da652d63 Andy McCall ↶ 25
    /// Prune unreachable objects older than the grace period (`now`
26
    /// ignores it), then repack everything into one pack (spec §13.4).
ba160f60 Andy McCall ↶ 27
    pub fn gc(&self, now: bool) -> Result<GcReport> {
28
        let keep = self.reachable()?;
75167672 Andy McCall 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
        };
ba160f60 Andy McCall ↶ 34
        let pruned = self.store.prune(&keep, grace)?;
da652d63 Andy McCall ↶ 35
        // Then everything left in one pack, compressed harder than a
36
        // commit's (spec §13.4), as `git gc` repacks.
37
        let (packed, merged_packs) = self.store.repack_all(crate::store::REPACK_ZSTD_LEVEL)?;
75167672 Andy McCall 38
        Ok(GcReport {
39
            packed,
40
            pruned,
41
            reachable: keep.len(),
42
            merged_packs,
43
        })
ba160f60 Andy McCall ↶ 44
    }
45
46
    /// Every object that must survive garbage collection.
47
    pub fn reachable(&self) -> Result<HashSet<ObjectRef>> {
48
        let mut keep: HashSet<ObjectRef> = HashSet::new();
49
        let mut states: Vec<ObjectRef> = Vec::new();
50
        let mut changes: Vec<ChangeHash> = Vec::new();
51
        let mut attests: Vec<ObjectRef> = Vec::new();
52
        let mut policies: Vec<ObjectRef> = Vec::new();
53
54
        // Operations: always kept; views within retention are roots.
75167672 Andy McCall 55
        let cutoff = Hlc::now()
56
            .wall_ms
57
            .saturating_sub(self.config.op_retention_days * 86_400_000);
ba160f60 Andy McCall ↶ 58
        for (i, (r, op)) in self.op_log()?.into_iter().enumerate() {
59
            keep.insert(r);
60
            if i == 0 || op.hlc.wall_ms >= cutoff {
61
                states.extend(op.view.bookmarks.values().copied());
62
                states.extend(op.view.remotes.values().flat_map(|b| b.values().copied()));
63
                changes.extend(op.view.workspaces.values().copied());
64
                policies.extend(op.view.policy);
65
            }
66
        }
252e8cb2 Andy McCall ↶ 67
        // Checkpoints: the object, its payload blob, and the blob's chunks.
68
        for r in self.checkpoint_refs()? {
69
            if let Ok(bytes) = self.store.get_typed(&r, ObjectType::Checkpoint) {
70
                keep.insert(r);
71
                let cp = crate::objects::Checkpoint::decode(&bytes)?;
72
                keep.insert(cp.payload);
73
                if let Ok(b) = self.store.get_typed(&cp.payload, ObjectType::Blob) {
74
                    keep.extend(Blob::decode(&b)?.chunks);
75
                }
afaba5f1 Andy McCall ↶ 76
                // A version 2 payload's graph blobs and their chunks.
77
                for g in self.checkpoint_graph_blobs(&cp).unwrap_or_default() {
78
                    keep.insert(g);
79
                    if let Ok(b) = self.store.get_typed(&g, ObjectType::Blob) {
80
                        keep.extend(Blob::decode(&b)?.chunks);
81
                    }
82
                }
252e8cb2 Andy McCall ↶ 83
            }
84
        }
132b884b Andy McCall ↶ 85
        // Tags: their states and attestations.
86
        let view_now = self.view()?;
87
        states.extend(view_now.tags.values().copied());
88
        for name in view_now.tags.keys() {
89
            if let Some((r, _)) = self.tag_attest(name)? {
90
                attests.push(r);
91
            }
92
        }
ba160f60 Andy McCall ↶ 93
        // Reference attestations.
94
        for name in self.view()?.bookmarks.keys() {
95
            if let Some((r, _)) = self.latest_ref_attest(name)? {
96
                attests.push(r);
97
            }
98
        }
21b2aec2 Andy McCall ↶ 99
        // A historical view may name objects pruned under an earlier, shorter
100
        // retention; there is nothing to keep for those.
ba160f60 Andy McCall ↶ 101
        for st in states {
102
            if keep.insert(st) {
21b2aec2 Andy McCall ↶ 103
                match self.store.get_state(&st) {
104
                    Ok(s) => changes.extend(s.heads),
105
                    Err(StoreError::NotFound(_)) => continue,
106
                    Err(e) => return Err(e.into()),
107
                }
ba160f60 Andy McCall ↶ 108
            }
109
        }
110
        for p in policies {
111
            if keep.insert(p) {
21b2aec2 Andy McCall ↶ 112
                let bytes = match self.store.get_typed(&p, ObjectType::Policy) {
113
                    Ok(b) => b,
114
                    Err(StoreError::NotFound(_)) => continue,
115
                    Err(e) => return Err(e.into()),
116
                };
117
                let policy = crate::policy::Policy::decode(&bytes)?;
ba160f60 Andy McCall ↶ 118
                for id in &policy.identities {
119
                    keep.insert(id.hash());
120
                }
121
                for sig in self.store.signatures(&p)? {
122
                    keep.insert(sig.key);
123
                }
124
            }
125
        }
126
        // Changes: their closure, identities, blobs, chunks, seals.
127
        let mut stack = changes;
128
        while let Some(h) = stack.pop() {
129
            if !keep.insert(h) {
130
                continue;
131
            }
21b2aec2 Andy McCall ↶ 132
            // Beyond the shallow boundary, or pruned under an earlier
133
            // retention, there is nothing to keep.
6f8aeb99 Andy McCall ↶ 134
            let c = match self.store.get_change_parts(&h) {
135
                Ok((c, texts)) => {
136
                    keep.extend(texts);
137
                    c
138
                }
2863456a Andy McCall ↶ 139
                Err(StoreError::NotFound(_)) => continue,
140
                Err(e) => return Err(e.into()),
141
            };
ba160f60 Andy McCall ↶ 142
            stack.extend(c.deps.iter().copied());
143
            if !c.author.is_zero() {
144
                keep.insert(c.author);
145
            }
146
            for sig in self.store.signatures(&h)? {
147
                keep.insert(sig.key);
148
            }
149
            for e in &c.edits {
150
                if let Edit::Replace { old, new, .. } = e {
151
                    for b in [old, new].into_iter().flatten() {
152
                        if keep.insert(*b) {
153
                            if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
154
                                keep.extend(Blob::decode(&bytes)?.chunks);
155
                            }
156
                        }
157
                    }
158
                }
159
            }
160
            attests.extend(self.store.seals_for(&h)?);
161
        }
fc1a466a Andy McCall ↶ 162
        self.reachable_receipts(&mut keep)?;
ba160f60 Andy McCall ↶ 163
        for a in attests {
164
            if keep.insert(a) {
165
                for sig in self.store.signatures(&a)? {
166
                    keep.insert(sig.key);
167
                }
168
            }
169
        }
170
        Ok(keep)
171
    }
172
}