solana_accounts_db/
utils.rs1#[cfg(target_os = "linux")]
2use {crate::io_uring::dir_remover::RingDirRemover, agave_io_uring::io_uring_supported};
3use {
4 log::*,
5 solana_measure::measure_time,
6 std::{
7 collections::HashSet,
8 fs, io,
9 path::{Path, PathBuf},
10 sync::Mutex,
11 thread,
12 },
13};
14
15pub const ACCOUNTS_RUN_DIR: &str = "run";
16pub const ACCOUNTS_SNAPSHOT_DIR: &str = "snapshot";
17
18pub fn create_all_accounts_run_and_snapshot_dirs(
22 account_paths: &[PathBuf],
23) -> std::io::Result<(Vec<PathBuf>, Vec<PathBuf>)> {
24 let mut run_dirs = Vec::with_capacity(account_paths.len());
25 let mut snapshot_dirs = Vec::with_capacity(account_paths.len());
26 for account_path in account_paths {
27 let (run_dir, snapshot_dir) = create_accounts_run_and_snapshot_dirs(account_path)?;
29 run_dirs.push(run_dir);
30 snapshot_dirs.push(snapshot_dir);
31 }
32 Ok((run_dirs, snapshot_dirs))
33}
34
35pub fn create_accounts_run_and_snapshot_dirs(
41 account_dir: impl AsRef<Path>,
42) -> std::io::Result<(PathBuf, PathBuf)> {
43 let run_path = account_dir.as_ref().join(ACCOUNTS_RUN_DIR);
44 let snapshot_path = account_dir.as_ref().join(ACCOUNTS_SNAPSHOT_DIR);
45 if (!run_path.is_dir()) || (!snapshot_path.is_dir()) {
46 if fs::remove_dir_all(&account_dir).is_err() {
54 remove_dir_contents(&account_dir);
55 }
56 fs::create_dir_all(&run_path)?;
57 fs::create_dir_all(&snapshot_path)?;
58 }
59
60 Ok((run_path, snapshot_path))
61}
62
63pub fn move_and_async_delete_path_contents(path: impl AsRef<Path>) {
66 move_and_async_delete_path(&path);
67 _ = std::fs::create_dir(path);
71}
72
73pub fn move_and_async_delete_path(path: impl AsRef<Path>) {
79 static IN_PROGRESS_DELETES: std::sync::LazyLock<Mutex<HashSet<PathBuf>>> =
80 std::sync::LazyLock::new(|| Mutex::new(HashSet::new()));
81
82 let mut lock = IN_PROGRESS_DELETES.lock().unwrap();
84
85 if !path.as_ref().exists() {
87 return;
88 }
89
90 if lock.contains(path.as_ref()) {
93 return;
94 }
95
96 let mut path_delete = path.as_ref().to_path_buf();
97 path_delete.set_file_name(format!(
98 "{}{}",
99 path_delete.file_name().unwrap().to_str().unwrap(),
100 "_to_be_deleted"
101 ));
102 if let Err(err) = fs::rename(&path, &path_delete) {
103 warn!(
104 "Cannot async delete, retrying in sync mode: failed to rename '{}' to '{}': {err}",
105 path.as_ref().display(),
106 path_delete.display(),
107 );
108 lock.insert(path.as_ref().to_path_buf());
111 drop(lock); remove_dir_contents(&path);
114 IN_PROGRESS_DELETES.lock().unwrap().remove(path.as_ref());
115 return;
116 }
117
118 lock.insert(path_delete.clone());
119 drop(lock);
120 thread::Builder::new()
121 .name("solDeletePath".to_string())
122 .spawn(move || {
123 trace!("background deleting {}...", path_delete.display());
124 let (result, measure_delete) = measure_time!(remove_dir_all(&path_delete));
125 if let Err(err) = result {
126 panic!("Failed to async delete '{}': {err}", path_delete.display());
127 }
128 trace!(
129 "background deleting {}... Done, and{measure_delete}",
130 path_delete.display()
131 );
132
133 IN_PROGRESS_DELETES.lock().unwrap().remove(&path_delete);
134 })
135 .expect("spawn background delete thread");
136}
137
138pub fn remove_dir_all(path: impl Into<PathBuf> + AsRef<Path>) -> io::Result<()> {
140 #[cfg(target_os = "linux")]
141 if io_uring_supported() {
142 if let Ok(mut remover) = RingDirRemover::new() {
143 return remover.remove_dir_all(path);
144 }
145 }
146
147 fs::remove_dir_all(path)
148}
149
150pub fn remove_dir_contents(path: impl AsRef<Path>) {
152 let path = path.as_ref();
153
154 #[cfg(target_os = "linux")]
155 if io_uring_supported() {
156 if let Ok(mut remover) = RingDirRemover::new() {
157 if let Err(e) = remover.remove_dir_contents(path) {
158 warn!("Failed to delete contents of '{}': {e}", path.display());
159 }
160
161 return;
162 }
163 }
164
165 remove_dir_contents_slow(path)
166}
167
168fn remove_dir_contents_slow(path: impl AsRef<Path>) {
173 match fs::read_dir(&path) {
174 Err(err) => {
175 warn!(
176 "Failed to delete contents of '{}': could not read dir: {err}",
177 path.as_ref().display(),
178 )
179 }
180 Ok(dir_entries) => {
181 for entry in dir_entries.flatten() {
182 let sub_path = entry.path();
183 let result = if sub_path.is_dir() {
184 fs::remove_dir_all(&sub_path)
185 } else {
186 fs::remove_file(&sub_path)
187 };
188 if let Err(err) = result {
189 warn!(
190 "Failed to delete contents of '{}': {err}",
191 sub_path.display(),
192 );
193 }
194 }
195 }
196 }
197}
198
199pub fn create_and_canonicalize_directories(
201 directories: impl IntoIterator<Item = impl AsRef<Path>>,
202) -> io::Result<Vec<PathBuf>> {
203 directories
204 .into_iter()
205 .map(create_and_canonicalize_directory)
206 .collect()
207}
208
209pub fn create_and_canonicalize_directory(directory: impl AsRef<Path>) -> io::Result<PathBuf> {
211 fs::create_dir_all(&directory)?;
212 fs::canonicalize(directory)
213}
214
215#[cfg(test)]
216mod tests {
217 use {super::*, tempfile::TempDir};
218
219 #[test]
220 pub fn test_create_all_accounts_run_and_snapshot_dirs() {
221 let (_tmp_dirs, account_paths): (Vec<TempDir>, Vec<PathBuf>) = (0..4)
222 .map(|_| {
223 let tmp_dir = tempfile::TempDir::new().unwrap();
224 let account_path = tmp_dir.path().join("accounts");
225 (tmp_dir, account_path)
226 })
227 .unzip();
228
229 let (account_run_paths, account_snapshot_paths) =
231 create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap();
232 account_run_paths.iter().all(|path| path.is_dir());
233 account_snapshot_paths.iter().all(|path| path.is_dir());
234
235 let account_path_first = account_paths.first().unwrap();
237 remove_dir_contents(account_path_first);
238 assert!(account_path_first.exists());
239 assert!(!account_path_first.join(ACCOUNTS_RUN_DIR).exists());
240 assert!(!account_path_first.join(ACCOUNTS_SNAPSHOT_DIR).exists());
241
242 _ = create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap();
243 account_run_paths.iter().all(|path| path.is_dir());
244 account_snapshot_paths.iter().all(|path| path.is_dir());
245 }
246}