Skip to main content

cherenkov/model/index/
gc.rs

1use super::{GcReport, ModelIndex, Removal, lookup, store};
2use anyhow::{Context, Result, ensure};
3
4impl ModelIndex {
5    /// Remove a model registration, or release only its retained source reference.
6    /// Source-only removal first checks the prepared checkpoint. Files remain until
7    /// [`Self::gc`] collects unreferenced owned artifacts; external files are untouched.
8    pub fn remove(&self, reference: &str, source_only: bool) -> Result<Removal> {
9        let mut catalog = self.lock()?;
10        let model = lookup(&catalog.value, reference)?.clone();
11        let mut released = Vec::new();
12
13        if source_only {
14            let prepared = model
15                .prepared
16                .as_ref()
17                .context("prepare the model before releasing source copies")?;
18            let artifact = &catalog.value.artifacts[prepared];
19
20            ensure!(artifact.ready, "prepared artifact is incomplete");
21
22            crate::model::Checkpoint::open(&self.artifact_path(artifact))
23                .context("validate the prepared model before releasing its source")?;
24
25            if let Some(id) = model.retained_source {
26                released.push(id);
27            }
28
29            catalog
30                .value
31                .models
32                .get_mut(&model.id)
33                .unwrap()
34                .retained_source = None;
35        } else {
36            released.extend(model.prepared);
37            released.extend(model.retained_source);
38            catalog.value.models.remove(&model.id);
39        }
40
41        catalog.save()?;
42
43        Ok(Removal {
44            id: model.id,
45            source_only,
46            released_artifacts: released,
47        })
48    }
49
50    /// Collect unreferenced catalog artifacts that have no active lease.
51    /// Owned directories are deleted; external records are removed without deleting
52    /// their files. A dry run reports candidates without removing either.
53    pub fn gc(&self, dry_run: bool) -> Result<GcReport> {
54        if !self.paths.data.join("index.json").exists() {
55            return Ok(GcReport {
56                dry_run,
57                ..Default::default()
58            });
59        }
60
61        let mut catalog = self.lock()?;
62        let candidates: Vec<_> = catalog
63            .value
64            .artifacts
65            .values()
66            .filter(|a| store::references(&catalog.value, &a.id) == 0)
67            .cloned()
68            .collect();
69        let mut report = GcReport {
70            dry_run,
71            ..Default::default()
72        };
73
74        for artifact in candidates {
75            let lease = self.lease_file(&artifact.id)?;
76
77            if lease.try_lock().is_err() {
78                report.leased.push(artifact.id);
79
80                continue;
81            }
82
83            if artifact.external.is_none() {
84                let path = self.artifact_root(&artifact.id);
85                report.candidate_bytes += store::bytes(&path)?;
86
87                if !dry_run && path.exists() {
88                    std::fs::remove_dir_all(path)?;
89                }
90            }
91
92            report.artifacts.push(artifact.id.clone());
93
94            if !dry_run {
95                catalog.value.artifacts.remove(&artifact.id);
96            }
97        }
98
99        if !dry_run {
100            catalog.save()?;
101        }
102
103        Ok(report)
104    }
105}