solana_accounts_db/
utils.rs

1use {
2    agave_fs::dirs,
3    log::*,
4    solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
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
18/// For all account_paths, create the run/ and snapshot/ sub directories.
19/// If an account_path directory does not exist, create it.
20/// It returns (account_run_paths, account_snapshot_paths) or error
21pub 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        // create the run/ and snapshot/ sub directories for each account_path
28        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
35/// To allow generating a bank snapshot directory with full state information, we need to
36/// hardlink account appendvec files from the runtime operation directory to a snapshot
37/// hardlink directory.  This is to create the run/ and snapshot sub directories for an
38/// account_path provided by the user.  These two sub directories are on the same file
39/// system partition to allow hard-linking.
40pub 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 the "run/" or "snapshot" sub directories do not exist, the directory may be from
47        // an older version for which the appendvec files are at this directory.  Clean up
48        // them first.
49        // This will be done only once when transitioning from an old image without run directory
50        // to this new version using run and snapshot directories.
51        // The run/ content cleanup will be done at a later point.  The snapshot/ content persists
52        // across the process boot, and will be purged by the account_background_service.
53        if fs::remove_dir_all(&account_dir).is_err() {
54            dirs::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
63/// Moves and asynchronously deletes the contents of a directory to avoid blocking on it.
64/// The directory is re-created after the move, and should now be empty.
65pub fn move_and_async_delete_path_contents(path: impl AsRef<Path>) {
66    move_and_async_delete_path(&path);
67    // The following could fail if the rename failed.
68    // If that happens, the directory should be left as is.
69    // So we ignore errors here.
70    _ = std::fs::create_dir(path);
71}
72
73/// Delete directories/files asynchronously to avoid blocking on it.
74/// First, in sync context, check if the original path exists, if it
75/// does, rename the original path to *_to_be_deleted.
76/// If there's an in-progress deleting thread for this path, return.
77/// Then spawn a thread to delete the renamed path.
78pub 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    // 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        dirs::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!(dirs::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/// Creates `directories` if they do not exist, and canonicalizes their paths
139pub fn create_and_canonicalize_directories(
140    directories: impl IntoIterator<Item = impl AsRef<Path>>,
141) -> io::Result<Vec<PathBuf>> {
142    directories
143        .into_iter()
144        .map(create_and_canonicalize_directory)
145        .collect()
146}
147
148/// Creates `directory` if it does not exist, and canonicalizes its path
149pub fn create_and_canonicalize_directory(directory: impl AsRef<Path>) -> io::Result<PathBuf> {
150    fs::create_dir_all(&directory)?;
151    fs::canonicalize(directory)
152}
153
154/// Creates a new AccountSharedData structure for anything that implements ReadableAccount.
155/// This function implies data copies.
156pub fn create_account_shared_data(account: &impl ReadableAccount) -> AccountSharedData {
157    AccountSharedData::create(
158        account.lamports(),
159        account.data().to_vec(),
160        *account.owner(),
161        account.executable(),
162        account.rent_epoch(),
163    )
164}
165
166#[cfg(test)]
167mod tests {
168    use {super::*, tempfile::TempDir};
169
170    #[test]
171    pub fn test_create_all_accounts_run_and_snapshot_dirs() {
172        let (_tmp_dirs, account_paths): (Vec<TempDir>, Vec<PathBuf>) = (0..4)
173            .map(|_| {
174                let tmp_dir = tempfile::TempDir::new().unwrap();
175                let account_path = tmp_dir.path().join("accounts");
176                (tmp_dir, account_path)
177            })
178            .unzip();
179
180        // create the `run/` and `snapshot/` dirs, and ensure they're there
181        let (account_run_paths, account_snapshot_paths) =
182            create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap();
183        account_run_paths.iter().all(|path| path.is_dir());
184        account_snapshot_paths.iter().all(|path| path.is_dir());
185
186        // delete a `run/` and `snapshot/` dir, then re-create it
187        let account_path_first = account_paths.first().unwrap();
188        dirs::remove_dir_contents(account_path_first);
189        assert!(account_path_first.exists());
190        assert!(!account_path_first.join(ACCOUNTS_RUN_DIR).exists());
191        assert!(!account_path_first.join(ACCOUNTS_SNAPSHOT_DIR).exists());
192
193        _ = create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap();
194        account_run_paths.iter().all(|path| path.is_dir());
195        account_snapshot_paths.iter().all(|path| path.is_dir());
196    }
197}