solana_accounts_db/
utils.rs

1use {
2    lazy_static,
3    log::*,
4    solana_measure::measure_time,
5    std::{
6        collections::HashSet,
7        fs, io,
8        path::{Path, PathBuf},
9        sync::Mutex,
10        thread,
11    },
12};
13
14pub const ACCOUNTS_RUN_DIR: &str = "run";
15pub const ACCOUNTS_SNAPSHOT_DIR: &str = "snapshot";
16
17/// For all account_paths, create the run/ and snapshot/ sub directories.
18/// If an account_path directory does not exist, create it.
19/// It returns (account_run_paths, account_snapshot_paths) or error
20pub fn create_all_accounts_run_and_snapshot_dirs(
21    account_paths: &[PathBuf],
22) -> std::io::Result<(Vec<PathBuf>, Vec<PathBuf>)> {
23    let mut run_dirs = Vec::with_capacity(account_paths.len());
24    let mut snapshot_dirs = Vec::with_capacity(account_paths.len());
25    for account_path in account_paths {
26        // create the run/ and snapshot/ sub directories for each account_path
27        let (run_dir, snapshot_dir) = create_accounts_run_and_snapshot_dirs(account_path)?;
28        run_dirs.push(run_dir);
29        snapshot_dirs.push(snapshot_dir);
30    }
31    Ok((run_dirs, snapshot_dirs))
32}
33
34/// To allow generating a bank snapshot directory with full state information, we need to
35/// hardlink account appendvec files from the runtime operation directory to a snapshot
36/// hardlink directory.  This is to create the run/ and snapshot sub directories for an
37/// account_path provided by the user.  These two sub directories are on the same file
38/// system partition to allow hard-linking.
39pub fn create_accounts_run_and_snapshot_dirs(
40    account_dir: impl AsRef<Path>,
41) -> std::io::Result<(PathBuf, PathBuf)> {
42    let run_path = account_dir.as_ref().join(ACCOUNTS_RUN_DIR);
43    let snapshot_path = account_dir.as_ref().join(ACCOUNTS_SNAPSHOT_DIR);
44    if (!run_path.is_dir()) || (!snapshot_path.is_dir()) {
45        // If the "run/" or "snapshot" sub directories do not exist, the directory may be from
46        // an older version for which the appendvec files are at this directory.  Clean up
47        // them first.
48        // This will be done only once when transitioning from an old image without run directory
49        // to this new version using run and snapshot directories.
50        // The run/ content cleanup will be done at a later point.  The snapshot/ content persists
51        // across the process boot, and will be purged by the account_background_service.
52        if fs::remove_dir_all(&account_dir).is_err() {
53            delete_contents_of_path(&account_dir);
54        }
55        fs::create_dir_all(&run_path)?;
56        fs::create_dir_all(&snapshot_path)?;
57    }
58
59    Ok((run_path, snapshot_path))
60}
61
62/// Moves and asynchronously deletes the contents of a directory to avoid blocking on it.
63/// The directory is re-created after the move, and should now be empty.
64pub fn move_and_async_delete_path_contents(path: impl AsRef<Path>) {
65    move_and_async_delete_path(&path);
66    // The following could fail if the rename failed.
67    // If that happens, the directory should be left as is.
68    // So we ignore errors here.
69    _ = std::fs::create_dir(path);
70}
71
72/// Delete directories/files asynchronously to avoid blocking on it.
73/// First, in sync context, check if the original path exists, if it
74/// does, rename the original path to *_to_be_deleted.
75/// If there's an in-progress deleting thread for this path, return.
76/// Then spawn a thread to delete the renamed path.
77pub fn move_and_async_delete_path(path: impl AsRef<Path>) {
78    lazy_static! {
79        static ref IN_PROGRESS_DELETES: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
80    };
81
82    // Grab the mutex so no new async delete threads can be spawned for this path.
83    let mut lock = IN_PROGRESS_DELETES.lock().unwrap();
84
85    // If the path does not exist, there's nothing to delete.
86    if !path.as_ref().exists() {
87        return;
88    }
89
90    // If the original path (`pathbuf` here) is already being deleted,
91    // then the path should not be moved and deleted again.
92    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        // Although the delete here is synchronous, we want to prevent another thread
109        // from moving & deleting this directory via `move_and_async_delete_path`.
110        lock.insert(path.as_ref().to_path_buf());
111        drop(lock); // unlock before doing sync delete
112
113        delete_contents_of_path(&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!(fs::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
138/// Delete the files and subdirectories in a directory.
139/// This is useful if the process does not have permission
140/// to delete the top level directory it might be able to
141/// delete the contents of that directory.
142pub fn delete_contents_of_path(path: impl AsRef<Path>) {
143    match fs::read_dir(&path) {
144        Err(err) => {
145            warn!(
146                "Failed to delete contents of '{}': could not read dir: {err}",
147                path.as_ref().display(),
148            )
149        }
150        Ok(dir_entries) => {
151            for entry in dir_entries.flatten() {
152                let sub_path = entry.path();
153                let result = if sub_path.is_dir() {
154                    fs::remove_dir_all(&sub_path)
155                } else {
156                    fs::remove_file(&sub_path)
157                };
158                if let Err(err) = result {
159                    warn!(
160                        "Failed to delete contents of '{}': {err}",
161                        sub_path.display(),
162                    );
163                }
164            }
165        }
166    }
167}
168
169/// Creates `directories` if they do not exist, and canonicalizes their paths
170pub fn create_and_canonicalize_directories(
171    directories: impl IntoIterator<Item = impl AsRef<Path>>,
172) -> io::Result<Vec<PathBuf>> {
173    directories
174        .into_iter()
175        .map(create_and_canonicalize_directory)
176        .collect()
177}
178
179/// Creates `directory` if it does not exist, and canonicalizes its path
180pub fn create_and_canonicalize_directory(directory: impl AsRef<Path>) -> io::Result<PathBuf> {
181    fs::create_dir_all(&directory)?;
182    fs::canonicalize(directory)
183}
184
185#[cfg(test)]
186mod tests {
187    use {super::*, tempfile::TempDir};
188
189    #[test]
190    pub fn test_create_all_accounts_run_and_snapshot_dirs() {
191        let (_tmp_dirs, account_paths): (Vec<TempDir>, Vec<PathBuf>) = (0..4)
192            .map(|_| {
193                let tmp_dir = tempfile::TempDir::new().unwrap();
194                let account_path = tmp_dir.path().join("accounts");
195                (tmp_dir, account_path)
196            })
197            .unzip();
198
199        // create the `run/` and `snapshot/` dirs, and ensure they're there
200        let (account_run_paths, account_snapshot_paths) =
201            create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap();
202        account_run_paths.iter().all(|path| path.is_dir());
203        account_snapshot_paths.iter().all(|path| path.is_dir());
204
205        // delete a `run/` and `snapshot/` dir, then re-create it
206        let account_path_first = account_paths.first().unwrap();
207        delete_contents_of_path(account_path_first);
208        assert!(account_path_first.exists());
209        assert!(!account_path_first.join(ACCOUNTS_RUN_DIR).exists());
210        assert!(!account_path_first.join(ACCOUNTS_SNAPSHOT_DIR).exists());
211
212        _ = create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap();
213        account_run_paths.iter().all(|path| path.is_dir());
214        account_snapshot_paths.iter().all(|path| path.is_dir());
215    }
216}