fedimint_cli/
db_locked.rs

1use std::path::Path;
2
3use anyhow::Context;
4use fedimint_core::db::IRawDatabase;
5use fedimint_core::task::block_in_place;
6use fedimint_core::{apply, async_trait_maybe_send};
7use fedimint_logging::LOG_CLIENT;
8use tracing::{debug, info};
9
10/// Locked version of database
11///
12/// This will use file-system advisory locks to prevent to
13/// serialize opening and using the `DB`.
14///
15/// Use [`LockedBuilder`] to create.
16#[derive(Debug)]
17pub struct Locked<DB> {
18    inner: DB,
19    #[allow(dead_code)] // only for `Drop`
20    lock: fs_lock::FileLock,
21}
22
23/// Builder for [`Locked`]
24pub struct LockedBuilder {
25    lock: fs_lock::FileLock,
26}
27
28impl LockedBuilder {
29    /// Create a [`Self`] by acquiring a lock file
30    pub fn new(lock_path: &Path) -> anyhow::Result<LockedBuilder> {
31        block_in_place(|| {
32            let file = std::fs::OpenOptions::new()
33                .write(true)
34                .create(true)
35                .truncate(true)
36                .open(lock_path)
37                .with_context(|| format!("Failed to open {}", lock_path.display()))?;
38
39            debug!(target: LOG_CLIENT, "Acquiring database lock");
40
41            let lock = match fs_lock::FileLock::new_try_exclusive(file) {
42                Ok(lock) => lock,
43                Err((file, _)) => {
44                    info!(target: LOG_CLIENT, "Waiting for the database lock");
45
46                    fs_lock::FileLock::new_exclusive(file)
47                        .context("Failed to acquire a lock file")?
48                }
49            };
50            debug!(target: LOG_CLIENT, "Acquired database lock");
51
52            Ok(LockedBuilder { lock })
53        })
54    }
55
56    /// Create [`Locked`] by giving it the database to wrap
57    pub fn with_db<DB>(self, db: DB) -> Locked<DB> {
58        Locked {
59            inner: db,
60            lock: self.lock,
61        }
62    }
63}
64
65#[apply(async_trait_maybe_send!)]
66impl<DB> IRawDatabase for Locked<DB>
67where
68    DB: IRawDatabase,
69{
70    type Transaction<'a> = DB::Transaction<'a>;
71
72    async fn begin_transaction<'a>(
73        &'a self,
74    ) -> <Locked<DB> as fedimint_core::db::IRawDatabase>::Transaction<'_> {
75        self.inner.begin_transaction().await
76    }
77
78    fn checkpoint(&self, backup_path: &Path) -> anyhow::Result<()> {
79        self.inner.checkpoint(backup_path)
80    }
81}