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 }