/ fedimint-cli / src / db_locked.rs
db_locked.rs
 1  use std::path::Path;
 2  
 3  use anyhow::Context;
 4  use fedimint_core::db::IRawDatabase;
 5  use fedimint_core::task::block_in_place;
 6  use fedimint_core::{apply, async_trait_maybe_send};
 7  use fedimint_logging::LOG_CLIENT;
 8  use 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)]
17  pub struct Locked<DB> {
18      inner: DB,
19      #[allow(dead_code)] // only for `Drop`
20      lock: fs_lock::FileLock,
21  }
22  
23  /// Builder for [`Locked`]
24  pub struct LockedBuilder {
25      lock: fs_lock::FileLock,
26  }
27  
28  impl LockedBuilder {
29      /// Create a [`Self`] by acquiring a lock file
30      pub async 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!)]
66  impl<DB> IRawDatabase for Locked<DB>
67  where
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  }