-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcmd_cache.rs
More file actions
128 lines (116 loc) · 4.91 KB
/
Copy pathcmd_cache.rs
File metadata and controls
128 lines (116 loc) · 4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::{
collections::HashSet,
time::{Duration, SystemTime},
};
use anyhow::anyhow;
use crate::{Context, Error};
#[derive(Debug, clap::Subcommand)]
pub enum CacheArgs {
/// Remove stale entries from the local cache
///
/// Deletes cached build artifacts whose last recorded use is older than
/// `--older-than`, or that have no recorded use at all. Packages needed
/// by the current project's tasks and stack are always kept. Also removes
/// sandbox, task, and temporary build directories whose owning process is
/// no longer running.
Clean {
/// Only delete cache entries last used at least this long ago.
/// Takes a whole number of days, hours, or minutes: e.g. `30d`,
/// `12h`, `45m`
#[arg(long, value_parser = parse_duration, default_value = "14d")]
older_than: Duration,
},
}
fn parse_duration(arg: &str) -> Result<std::time::Duration, anyhow::Error> {
if let Some(v) = arg.strip_suffix("d") {
let days: u64 = v.parse().map_err(|e| anyhow!("parsing days: {}", e))?;
Ok(std::time::Duration::from_hours(24 * days))
} else if let Some(v) = arg.strip_suffix("h") {
let hours: u64 = v.parse().map_err(|e| anyhow!("parsing hours: {}", e))?;
Ok(std::time::Duration::from_hours(hours))
} else if let Some(v) = arg.strip_suffix("m") {
let minutes: u64 = v.parse().map_err(|e| anyhow!("parsing minutes: {}", e))?;
Ok(std::time::Duration::from_mins(minutes))
} else {
Err(anyhow!("invalid duration: {}", arg))
}
}
pub async fn cmd_cache(args: CacheArgs, ctx: &mut Context) -> Result<(), Error> {
let graph = ctx.graph_from_all_packages()?;
let need_objs = ctx
.scaffolding_packages()?
.into_iter()
.map(|bsr| graph.spec_hash(&bsr))
.collect::<HashSet<_>>();
let cache = ctx.local_cache();
let rt = cache.atimes().unwrap();
let candidates = cache.iter_entries().filter_map(|e| {
if need_objs.contains(&e) {
None
} else {
let last_use = rt.last_read(&e);
Some((e, last_use))
}
});
let now = SystemTime::now();
match args {
CacheArgs::Clean { older_than } => {
let cutoff = now.checked_sub(older_than).unwrap();
for (spec_hash, last_used) in candidates {
if last_used.is_none() || last_used.as_ref().unwrap() < &cutoff {
let ident = if let Ok(meta) = cache.read_meta(&spec_hash) {
format!("{} [{}]", meta.inner, spec_hash.0)
} else {
format!("Object [{}]", spec_hash.0)
};
println!("Deleting {}", ident);
cache
.invalidate_dir(&spec_hash)
.map_err(|e| Error::Other(anyhow!(e)))?;
}
}
}
}
for sandbox in std::fs::read_dir(ctx.builds_base_dir())
.map_err(|e| Error::IO("reading sandboxes dir", ctx.builds_base_dir(), e))?
{
let entry = sandbox.map_err(|e| Error::IO("sandbox entry", ctx.builds_base_dir(), e))?;
cleanup_stale("sandbox", entry)?;
}
for task in std::fs::read_dir(ctx.tasks_base_dir())
.map_err(|e| Error::IO("reading tasks dir", ctx.tasks_base_dir(), e))?
{
let entry = task.map_err(|e| Error::IO("task entry", ctx.tasks_base_dir(), e))?;
cleanup_stale("task", entry)?;
}
for at in std::fs::read_dir(ctx.cache_base_dir().join("temp"))
.map_err(|e| Error::IO("reading artifact temp dir", ctx.tasks_base_dir(), e))?
{
let entry = at.map_err(|e| Error::IO("artifact temp entry", ctx.tasks_base_dir(), e))?;
cleanup_stale("tempdir", entry)?;
}
Ok(())
}
/// Remove a sandbox/task/temp directory whose owning process is gone.
///
/// Ownership comes from the marker the sandbox writes
/// ([`common::sandbox_owner`]), not from the trailing `-<pid>` in the directory
/// name. That suffix is the pid of whatever *created* the sandbox, which is the
/// right answer only when the creator is also the thing whose lifetime the
/// sandbox tracks — true for this CLI, false for a long-lived daemon. Reading
/// the marker means one rule works for both.
///
/// A directory with no marker is left alone: it predates the marker, or was
/// abandoned before its leader spawned, and neither is distinguishable here
/// from a sandbox mid-construction.
fn cleanup_stale(kind: &str, entry: std::fs::DirEntry) -> Result<(), Error> {
let name = entry.file_name();
let s = name.to_str().unwrap();
if common::sandbox_owner::owner_is_gone(&entry.path()) {
// No such proc entry, therefore the owner is dead. Clean up directory.
println!("Cleaning up stale {} {}", kind, s);
common::remove_dir_all(entry.path())
.map_err(|e| Error::IO("rm stale sandbox", entry.path(), e))?;
}
Ok(())
}