diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/rusqlite/src/unlock_notify.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rusqlite/src/unlock_notify.rs')
-rw-r--r-- | third_party/rust/rusqlite/src/unlock_notify.rs | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/third_party/rust/rusqlite/src/unlock_notify.rs b/third_party/rust/rusqlite/src/unlock_notify.rs new file mode 100644 index 0000000000..8fba6b3c7e --- /dev/null +++ b/third_party/rust/rusqlite/src/unlock_notify.rs @@ -0,0 +1,117 @@ +//! [Unlock Notification](http://sqlite.org/unlock_notify.html) + +use std::os::raw::c_int; +use std::os::raw::c_void; +use std::panic::catch_unwind; +use std::sync::{Condvar, Mutex}; + +use crate::ffi; + +struct UnlockNotification { + cond: Condvar, // Condition variable to wait on + mutex: Mutex<bool>, // Mutex to protect structure +} + +#[allow(clippy::mutex_atomic)] +impl UnlockNotification { + fn new() -> UnlockNotification { + UnlockNotification { + cond: Condvar::new(), + mutex: Mutex::new(false), + } + } + + fn fired(&self) { + let mut flag = unpoison(self.mutex.lock()); + *flag = true; + self.cond.notify_one(); + } + + fn wait(&self) { + let mut fired = unpoison(self.mutex.lock()); + while !*fired { + fired = unpoison(self.cond.wait(fired)); + } + } +} + +#[inline] +fn unpoison<T>(r: Result<T, std::sync::PoisonError<T>>) -> T { + r.unwrap_or_else(std::sync::PoisonError::into_inner) +} + +/// This function is an unlock-notify callback +unsafe extern "C" fn unlock_notify_cb(ap_arg: *mut *mut c_void, n_arg: c_int) { + use std::slice::from_raw_parts; + let args = from_raw_parts(ap_arg as *const &UnlockNotification, n_arg as usize); + for un in args { + drop(catch_unwind(std::panic::AssertUnwindSafe(|| un.fired()))); + } +} + +pub unsafe fn is_locked(db: *mut ffi::sqlite3, rc: c_int) -> bool { + rc == ffi::SQLITE_LOCKED_SHAREDCACHE + || (rc & 0xFF) == ffi::SQLITE_LOCKED + && ffi::sqlite3_extended_errcode(db) == ffi::SQLITE_LOCKED_SHAREDCACHE +} + +/// This function assumes that an SQLite API call (either `sqlite3_prepare_v2()` +/// or `sqlite3_step()`) has just returned `SQLITE_LOCKED`. The argument is the +/// associated database connection. +/// +/// This function calls `sqlite3_unlock_notify()` to register for an +/// unlock-notify callback, then blocks until that callback is delivered +/// and returns `SQLITE_OK`. The caller should then retry the failed operation. +/// +/// Or, if `sqlite3_unlock_notify()` indicates that to block would deadlock +/// the system, then this function returns `SQLITE_LOCKED` immediately. In +/// this case the caller should not retry the operation and should roll +/// back the current transaction (if any). +#[cfg(feature = "unlock_notify")] +pub unsafe fn wait_for_unlock_notify(db: *mut ffi::sqlite3) -> c_int { + let un = UnlockNotification::new(); + /* Register for an unlock-notify callback. */ + let rc = ffi::sqlite3_unlock_notify( + db, + Some(unlock_notify_cb), + &un as *const UnlockNotification as *mut c_void, + ); + debug_assert!( + rc == ffi::SQLITE_LOCKED || rc == ffi::SQLITE_LOCKED_SHAREDCACHE || rc == ffi::SQLITE_OK + ); + if rc == ffi::SQLITE_OK { + un.wait(); + } + rc +} + +#[cfg(test)] +mod test { + use crate::{Connection, OpenFlags, Result, Transaction, TransactionBehavior}; + use std::sync::mpsc::sync_channel; + use std::thread; + use std::time; + + #[test] + fn test_unlock_notify() -> Result<()> { + let url = "file::memory:?cache=shared"; + let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI; + let db1 = Connection::open_with_flags(url, flags)?; + db1.execute_batch("CREATE TABLE foo (x)")?; + let (rx, tx) = sync_channel(0); + let child = thread::spawn(move || { + let mut db2 = Connection::open_with_flags(url, flags).unwrap(); + let tx2 = Transaction::new(&mut db2, TransactionBehavior::Immediate).unwrap(); + tx2.execute_batch("INSERT INTO foo VALUES (42)").unwrap(); + rx.send(1).unwrap(); + let ten_millis = time::Duration::from_millis(10); + thread::sleep(ten_millis); + tx2.commit().unwrap(); + }); + assert_eq!(tx.recv().unwrap(), 1); + let the_answer: Result<i64> = db1.query_row("SELECT x FROM foo", [], |r| r.get(0)); + assert_eq!(42i64, the_answer?); + child.join().unwrap(); + Ok(()) + } +} |