fedimint_db_locked/
lib.rs1use std::path::{Path, PathBuf};
2
3use fedimint_core::db::IRawDatabase;
4use fedimint_core::{apply, async_trait_maybe_send};
5use fedimint_logging::LOG_DB;
6use tracing::{debug, info};
7
8#[derive(Debug)]
15pub struct Locked<DB> {
16 inner: DB,
17 #[allow(dead_code)] lock: fs_lock::FileLock,
19}
20
21pub struct LockedBuilder {
23 lock: fs_lock::FileLock,
24}
25
26impl LockedBuilder {
27 pub fn new(db_path: &Path) -> Result<LockedBuilder, DbLockError> {
29 let lock_path = db_path.with_extension("db.lock");
30 let file = std::fs::OpenOptions::new()
31 .write(true)
32 .create(true)
33 .truncate(true)
34 .open(&lock_path)
35 .map_err(|source| DbLockError::Open {
36 path: lock_path.clone(),
37 source,
38 })?;
39
40 debug!(target: LOG_DB, lock=%lock_path.display(), "Acquiring database lock");
41
42 let lock = match fs_lock::FileLock::new_try_exclusive(file) {
43 Ok(lock) => lock,
44 Err((file, _)) => {
45 info!(target: LOG_DB, lock=%lock_path.display(), "Waiting for the database lock");
46
47 fs_lock::FileLock::new_exclusive(file).map_err(DbLockError::Acquire)?
48 }
49 };
50 debug!(target: LOG_DB, lock=%lock_path.display(), "Acquired database lock");
51
52 Ok(LockedBuilder { lock })
53 }
54
55 pub fn with_db<DB, E>(self, db_fn: impl FnOnce() -> Result<DB, E>) -> Result<Locked<DB>, E> {
58 Ok(Locked {
59 inner: db_fn()?,
60 lock: self.lock,
61 })
62 }
63}
64
65#[derive(Debug, thiserror::Error)]
67#[non_exhaustive]
68pub enum DbLockError {
69 #[error("Failed to open {}", path.display())]
71 Open {
72 path: PathBuf,
74 #[source]
76 source: std::io::Error,
77 },
78
79 #[error("Failed to acquire a lock file")]
81 Acquire(#[source] std::io::Error),
82}
83
84#[apply(async_trait_maybe_send!)]
85impl<DB> IRawDatabase for Locked<DB>
86where
87 DB: IRawDatabase,
88{
89 type Transaction<'a> = DB::Transaction<'a>;
90
91 async fn begin_transaction<'a>(
92 &'a self,
93 ) -> <Locked<DB> as fedimint_core::db::IRawDatabase>::Transaction<'_> {
94 self.inner.begin_transaction().await
95 }
96
97 fn checkpoint(&self, backup_path: &Path) -> fedimint_core::db::DatabaseResult<()> {
98 self.inner.checkpoint(backup_path)
99 }
100}