OXYGENHUB

Coverage / crates/oxy-core/src/repo/gc.rs 91.3%

126 of 138 lines run, 6 of 12 functions · run #269 on main · run missed

1
//! Garbage collection (spec §21): 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 the one a full repack writes.
21
    pub merged_packs: usize,
22
}
23
24
impl Repo {
25
    /// Prune unreachable objects older than the grace period (`now`
26
    /// ignores it), then repack everything into one pack (spec §13.4).
2712×
    pub fn gc(&self, now: bool) -> Result<GcReport> {
2866×
        self.gc_with(now, &mut |_| {})
2912×
    }
30
31
    /// [`Repo::gc`], reporting each phase and the packing's progress
32
    /// (oxygen#66), for a spinner or a log.
3313×
    pub fn gc_with(&self, now: bool, progress: crate::store::Progress) -> Result<GcReport> {
3413×
        self.gc_keeping(now, &[], progress)
3513×
    }
36
37
    /// [`Repo::gc_with`], keeping `extra` blobs and their chunks as well:
38
    /// objects a host stored for itself and names from outside the
39
    /// repository's history, such as a forge's job artifacts
40
    /// (oxygenhub#126).
4114×
    pub fn gc_keeping(
4214×
        &self,
4314×
        now: bool,
4414×
        extra: &[ObjectRef],
4514×
        progress: crate::store::Progress,
4614×
    ) -> Result<GcReport> {
4714×
        progress("finding what is reachable");
4814×
        let mut keep = self.reachable()?;
4914×
        for r in extra {
501×
            if !self.store.has(r) || !keep.insert(*r) {
510×
                continue;
521×
            }
531×
            if let Ok(body) = self.store.get_typed(r, ObjectType::Blob) {
541×
                if let Ok(blob) = Blob::decode(&body) {
551×
                    keep.extend(blob.chunks);
561×
                }
570×
            }
58
        }
5914×
        let grace = if now {
6012×
            std::time::Duration::ZERO
61
        } else {
622×
            std::time::Duration::from_secs(self.config.gc_grace_days * 86_400)
63
        };
6414×
        let pruned = self.store.prune_with(&keep, grace, progress)?;
65
        // Then everything left in one pack, compressed harder than a
66
        // commit's (spec §13.4), as `git gc` repacks.
6714×
        let (packed, merged_packs) = self
6814×
            .store
6914×
            .repack_all_with(crate::store::REPACK_ZSTD_LEVEL, progress)?;
7014×
        Ok(GcReport {
7114×
            packed,
7214×
            pruned,
7314×
            reachable: keep.len(),
7414×
            merged_packs,
7514×
        })
7614×
    }
77
78
    /// Every object that must survive garbage collection.
7914×
    pub fn reachable(&self) -> Result<HashSet<ObjectRef>> {
8014×
        let mut keep: HashSet<ObjectRef> = HashSet::new();
8114×
        let mut states: Vec<ObjectRef> = Vec::new();
8214×
        let mut changes: Vec<ChangeHash> = Vec::new();
8314×
        let mut attests: Vec<ObjectRef> = Vec::new();
8414×
        let mut policies: Vec<ObjectRef> = Vec::new();
85
86
        // Operations: always kept; views within retention are roots.
8714×
        let cutoff = Hlc::now()
8814×
            .wall_ms
8914×
            .saturating_sub(self.config.op_retention_days * 86_400_000);
90108×
        for (i, (r, op)) in self.op_log()?.into_iter().enumerate() {
91108×
            keep.insert(r);
92108×
            if i == 0 || op.hlc.wall_ms >= cutoff {
9370×
                states.extend(op.view.bookmarks.values().copied());
9470×
                states.extend(op.view.remotes.values().flat_map(|b| b.values().copied()));
9570×
                changes.extend(op.view.workspaces.values().copied());
9670×
                policies.extend(op.view.policy);
9738×
            }
98
        }
99
        // Checkpoints: the object, its payload blob, and the blob's chunks.
10014×
        for r in self.checkpoint_refs()? {
1012×
            if let Ok(bytes) = self.store.get_typed(&r, ObjectType::Checkpoint) {
1022×
                keep.insert(r);
1032×
                let cp = crate::objects::Checkpoint::decode(&bytes)?;
1042×
                keep.insert(cp.payload);
1052×
                if let Ok(b) = self.store.get_typed(&cp.payload, ObjectType::Blob) {
1062×
                    keep.extend(Blob::decode(&b)?.chunks);
1070×
                }
108
                // A version 2 payload's graph blobs and their chunks.
1092×
                for g in self.checkpoint_graph_blobs(&cp).unwrap_or_default() {
1101×
                    keep.insert(g);
1111×
                    if let Ok(b) = self.store.get_typed(&g, ObjectType::Blob) {
1121×
                        keep.extend(Blob::decode(&b)?.chunks);
1130×
                    }
114
                }
1150×
            }
116
        }
117
        // Tags: their states and attestations.
11814×
        let view_now = self.view()?;
11914×
        states.extend(view_now.tags.values().copied());
12014×
        for name in view_now.tags.keys() {
1213×
            if let Some((r, _)) = self.tag_attest(name)? {
1223×
                attests.push(r);
1233×
            }
124
        }
125
        // Reference attestations.
12614×
        for name in self.view()?.bookmarks.keys() {
12714×
            if let Some((r, _)) = self.latest_ref_attest(name)? {
1280×
                attests.push(r);
12914×
            }
130
        }
131
        // A historical view may name objects pruned under an earlier, shorter
132
        // retention; there is nothing to keep for those.
13389×
        for st in states {
13489×
            if keep.insert(st) {
13543×
                match self.store.get_state(&st) {
13640×
                    Ok(s) => changes.extend(s.heads),
1373×
                    Err(StoreError::NotFound(_)) => continue,
1380×
                    Err(e) => return Err(e.into()),
139
                }
14046×
            }
141
        }
14214×
        for p in policies {
1434×
            if keep.insert(p) {
1441×
                let bytes = match self.store.get_typed(&p, ObjectType::Policy) {
1451×
                    Ok(b) => b,
1460×
                    Err(StoreError::NotFound(_)) => continue,
1470×
                    Err(e) => return Err(e.into()),
148
                };
1491×
                let policy = crate::policy::Policy::decode(&bytes)?;
1501×
                for id in &policy.identities {
1511×
                    keep.insert(id.hash());
1521×
                }
1531×
                for sig in self.store.signatures(&p)? {
1541×
                    keep.insert(sig.key);
1551×
                }
1563×
            }
157
        }
158
        // Changes: their closure, identities, blobs, chunks, seals.
15914×
        let mut stack = changes;
160228×
        while let Some(h) = stack.pop() {
161214×
            if !keep.insert(h) {
162109×
                continue;
163105×
            }
164
            // Beyond the shallow boundary, or pruned under an earlier
165
            // retention, there is nothing to keep.
166105×
            let c = match self.store.get_change_parts(&h) {
16795×
                Ok((c, texts)) => {
16895×
                    keep.extend(texts);
16995×
                    c
170
                }
17110×
                Err(StoreError::NotFound(_)) => continue,
1720×
                Err(e) => return Err(e.into()),
173
            };
17495×
            stack.extend(c.deps.iter().copied());
17595×
            if !c.author.is_zero() {
17630×
                keep.insert(c.author);
17765×
            }
17895×
            for sig in self.store.signatures(&h)? {
17913×
                keep.insert(sig.key);
18013×
            }
181108×
            for e in &c.edits {
182108×
                if let Edit::Replace { old, new, .. } = e {
18317×
                    for b in [old, new].into_iter().flatten() {
18417×
                        if keep.insert(*b) {
18517×
                            if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
18617×
                                keep.extend(Blob::decode(&bytes)?.chunks);
1870×
                            }
1880×
                        }
189
                    }
19091×
                }
191
            }
19295×
            attests.extend(self.store.seals_for(&h)?);
193
        }
19414×
        self.reachable_receipts(&mut keep)?;
19514×
        for a in attests {
1965×
            if keep.insert(a) {
1974×
                for sig in self.store.signatures(&a)? {
1984×
                    keep.insert(sig.key);
1994×
                }
2001×
            }
201
        }
20214×
        Ok(keep)
20314×
    }
204
}