fedimint_rocksdb/
db_locked.rs

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