evobench_tools/run/
bench_tmp_dir.rs1use std::{
2 collections::{HashMap, hash_map::Entry},
3 env::temp_dir,
4 fs::File,
5 io::Write,
6 mem::swap,
7 ops::Deref,
8 os::unix::fs::MetadataExt,
9 path::{Path, PathBuf},
10 sync::{Arc, Mutex},
11 thread::JoinHandle,
12 time::Duration,
13};
14
15use anyhow::{Result, anyhow, bail};
16use cj_path_util::path_util::AppendToPath;
17use nix::unistd::{getpid, getuid};
18use rand::Rng;
19
20use crate::{
21 ctx, info,
22 utillib::{into_arc_path::IntoArcPath, linux_mounts::MountPoints, user::get_username},
23 warn,
24};
25
26#[derive(Debug, PartialEq, Eq)]
31pub struct BenchTmpDir {
32 path: Arc<Path>,
33}
34
35impl AsRef<Path> for BenchTmpDir {
36 fn as_ref(&self) -> &Path {
37 &self.path
38 }
39}
40
41impl Deref for BenchTmpDir {
42 type Target = Path;
43
44 fn deref(&self) -> &Self::Target {
45 &self.path
46 }
47}
48
49fn _start_daemon(path: Arc<Path>, is_tmpfs: bool) -> Result<JoinHandle<()>, std::io::Error> {
50 let sleep_time = Duration::from_millis(if is_tmpfs { 100 } else { 10000 });
51
52 let th = std::thread::Builder::new().name("tmp-keep-alive".into());
53 th.spawn(move || -> () {
54 let pid = getpid();
55
56 let mut file_path_0 = path.append(format!(".{pid}.tmp-keep-alive-dir-0"));
57 {
58 if let Ok(mut f) = File::create(&file_path_0) {
59 _ = f.write_all(
60 "This file stays to ensure there is always a file--or now replaced\n"
61 .as_bytes(),
62 );
63 } else {
64 info!("could not create touch file {file_path_0:?}");
65 }
66 }
67 let mut file_path = path.append(format!(".{pid}.tmp-keep-alive-dir"));
68 _ = File::create(&file_path);
69 let mut rnd = rand::thread_rng();
70 while Arc::strong_count(&path) > 1 {
71 if rnd.gen_range(0..2) == 0 {
72 swap(&mut file_path_0, &mut file_path);
73 _ = std::fs::remove_file(&file_path);
74 }
75 match File::create(&file_path) {
76 Ok(mut f) => {
77 for _ in 0..20 {
78 let n = rnd.gen_range(0..10000000);
79 use std::io::Write;
80 if let Err(e) = write!(&mut f, "{n}\n") {
81 info!("could not write to touch file {file_path:?}: {e:#}");
82 break;
83 }
84
85 if !path.exists() {
86 match std::fs::create_dir(&path) {
88 Ok(_) => {
89 warn!(
90 "recreated directory {path:?}, worst of all \
91 total hacks with race condition"
92 );
93 }
94 Err(e) => {
95 warn!("could not even recreate directory {path:?}: {e:#}");
96 break;
97 }
98 }
99 }
100
101 std::thread::sleep(sleep_time);
102 }
103 }
104 Err(e) => {
105 warn!("could not create touch file {file_path:?}: {e:#}");
106 match std::fs::create_dir(&path) {
107 Ok(_) => {
108 warn!(
109 "recreated directory {path:?}, worst of all \
110 total hacks with race condition"
111 );
112 }
113 Err(e) => {
114 warn!("could not even recreate directory {path:?}: {e:#}");
115 break;
116 }
117 }
118 }
119 }
120 std::thread::sleep(sleep_time);
121 }
122 let mut daemons_guard = DAEMONS.lock().expect("no panics in this scope");
124 let daemons = daemons_guard
125 .as_mut()
126 .expect("already has hashmap since that happens before this thread is started");
127 daemons.remove(&path);
128 })
129}
130
131static DAEMONS: Mutex<Option<HashMap<Arc<Path>, Result<JoinHandle<()>, std::io::Error>>>> =
132 Mutex::new(None);
133
134fn start_daemon(path: Arc<Path>, is_tmpfs: bool) -> Result<()> {
135 let mut daemons = DAEMONS.lock().expect("no panics in this scope");
136 let m = daemons.get_or_insert_with(|| HashMap::new());
137 let r = match m.entry(path.clone()) {
138 Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
139 Entry::Vacant(vacant_entry) => vacant_entry.insert(_start_daemon(path, is_tmpfs)),
140 };
141 r.as_ref()
142 .map(|_| ())
143 .map_err(|e| anyhow!("start_daemon: {e:#}"))
144}
145
146pub fn get_fast_and_large_temp_dir_base() -> Result<(PathBuf, bool)> {
150 match std::env::consts::OS {
153 "linux" => {
154 let mount_points = MountPoints::read()?;
155
156 if let Some(tmp) = mount_points.get_by_path("/tmp") {
157 if tmp.is_tmpfs() {
158 let tmp_metadata = tmp.path_metadata()?;
159 if let Some(dev_shm) = mount_points.get_by_path("/dev/shm") {
160 let dev_shm_metadata = dev_shm.path_metadata()?;
161 if tmp_metadata.ino() == dev_shm_metadata.ino() {
162 return Ok((tmp.path_buf(), true));
163 }
164 }
165 return Ok((tmp.path_buf(), true));
167 } else {
168 if let Some(dev_shm) = mount_points.get_by_path("/dev/shm") {
169 return Ok((dev_shm.path_buf(), dev_shm.is_tmpfs()));
171 }
172 return Ok((tmp.path_buf(), false));
174 }
175 } else {
176 if let Some(dev_shm) = mount_points.get_by_path("/dev/shm") {
177 return Ok((dev_shm.path_buf(), dev_shm.is_tmpfs()));
179 }
180 return Ok((temp_dir(), false));
181 }
182 }
183 _ => Ok((temp_dir(), false)),
184 }
185}
186
187pub fn bench_tmp_dir() -> Result<BenchTmpDir> {
197 let (base, is_tmpfs) = get_fast_and_large_temp_dir_base()?;
198 let user = get_username()?;
199 let path = base.append(&user).into_arc_path();
200
201 match std::fs::create_dir(&path) {
202 Ok(()) => {
203 start_daemon(path.clone(), is_tmpfs)?;
204 Ok(BenchTmpDir { path })
205 }
206 Err(e) => match e.kind() {
207 std::io::ErrorKind::AlreadyExists => {
208 let m = std::fs::metadata(&path)?;
209 let dir_uid = m.uid();
210 let uid: u32 = getuid().into();
211 if dir_uid == uid {
212 start_daemon(path.clone(), is_tmpfs)?;
213 Ok(BenchTmpDir { path })
214 } else {
215 bail!(
216 "bench_tmp_dir: directory {path:?} should be owned by \
217 the user {user:?} which is set in the USER env var, \
218 but the uid owning that directory is {dir_uid} whereas \
219 the current process is running as {uid}"
220 )
221 }
222 }
223 _ => Err(e).map_err(ctx!("create_dir {path:?}")),
224 },
225 }
226}