prolly/prolly/content_graph/
gc.rs1use super::{walk_content_graph, ContentGraphLimits, TypedContentRoot};
2use crate::prolly::cid::Cid;
3use crate::prolly::error::Error;
4use crate::prolly::store::Store;
5use std::collections::HashSet;
6
7#[derive(Clone, Debug, Default, PartialEq, Eq)]
9pub struct ContentGcPlan {
10 pub live_cids: Vec<Cid>,
11 pub live_objects: usize,
12 pub live_bytes: usize,
13 pub candidate_objects: usize,
14 pub reclaimable_cids: Vec<Cid>,
15 pub reclaimable_bytes: usize,
16 pub missing_candidates: usize,
17}
18
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
21pub struct ContentGcSweep {
22 pub plan: ContentGcPlan,
23 pub deleted_objects: usize,
24 pub deleted_bytes: usize,
25}
26
27pub fn plan_content_gc<S: Store>(
28 store: &S,
29 retained_roots: &[TypedContentRoot],
30 candidates: &[Cid],
31 limits: &ContentGraphLimits,
32) -> Result<ContentGcPlan, Error> {
33 let walk = walk_content_graph(store, retained_roots, limits)?;
34 let mut live_cids: Vec<_> = walk
35 .objects
36 .iter()
37 .map(|object| object.root.cid.clone())
38 .collect();
39 live_cids.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
40 let live: HashSet<_> = live_cids.iter().cloned().collect();
41 let mut unique_candidates = candidates.to_vec();
42 unique_candidates.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
43 unique_candidates.dedup();
44 let mut reclaimable_cids = Vec::new();
45 let mut reclaimable_bytes = 0usize;
46 let mut missing_candidates = 0usize;
47 for cid in &unique_candidates {
48 if live.contains(cid) {
49 continue;
50 }
51 match store
52 .get(cid.as_bytes())
53 .map_err(|error| Error::Store(Box::new(error)))?
54 {
55 Some(bytes) => {
56 let actual = Cid::from_bytes(&bytes);
57 if actual != *cid {
58 return Err(Error::CidMismatch {
59 expected: cid.clone(),
60 actual,
61 });
62 }
63 reclaimable_bytes += bytes.len();
64 reclaimable_cids.push(cid.clone());
65 }
66 None => missing_candidates += 1,
67 }
68 }
69 Ok(ContentGcPlan {
70 live_objects: live_cids.len(),
71 live_cids,
72 live_bytes: walk.total_bytes,
73 candidate_objects: unique_candidates.len(),
74 reclaimable_cids,
75 reclaimable_bytes,
76 missing_candidates,
77 })
78}
79
80pub fn sweep_content_gc<S: Store>(
81 store: &S,
82 retained_roots: &[TypedContentRoot],
83 candidates: &[Cid],
84 limits: &ContentGraphLimits,
85) -> Result<ContentGcSweep, Error> {
86 sweep_content_gc_with_invalidator(store, retained_roots, candidates, limits, |_| {})
87}
88
89pub fn sweep_content_gc_with_invalidator<S, F>(
93 store: &S,
94 retained_roots: &[TypedContentRoot],
95 candidates: &[Cid],
96 limits: &ContentGraphLimits,
97 mut invalidate: F,
98) -> Result<ContentGcSweep, Error>
99where
100 S: Store,
101 F: FnMut(&Cid),
102{
103 let plan = plan_content_gc(store, retained_roots, candidates, limits)?;
104 for cid in &plan.reclaimable_cids {
105 store
106 .delete(cid.as_bytes())
107 .map_err(|error| Error::Store(Box::new(error)))?;
108 invalidate(cid);
109 }
110 Ok(ContentGcSweep {
111 deleted_objects: plan.reclaimable_cids.len(),
112 deleted_bytes: plan.reclaimable_bytes,
113 plan,
114 })
115}