1 //! Busy handler (when the database is locked) 2 use std::mem; 3 use std::os::raw::{c_int, c_void}; 4 use std::panic::catch_unwind; 5 use std::ptr; 6 use std::time::Duration; 7 8 use crate::ffi; 9 use crate::{Connection, InnerConnection, Result}; 10 11 impl Connection { 12 /// Set a busy handler that sleeps for a specified amount of time when a 13 /// table is locked. The handler will sleep multiple times until at 14 /// least "ms" milliseconds of sleeping have accumulated. 15 /// 16 /// Calling this routine with an argument equal to zero turns off all busy 17 /// handlers. 18 /// 19 /// There can only be a single busy handler for a particular database 20 /// connection at any given moment. If another busy handler was defined 21 /// (using [`busy_handler`](Connection::busy_handler)) prior to calling this 22 /// routine, that other busy handler is cleared. 23 /// 24 /// Newly created connections currently have a default busy timeout of 25 /// 5000ms, but this may be subject to change. busy_timeout(&self, timeout: Duration) -> Result<()>26 pub fn busy_timeout(&self, timeout: Duration) -> Result<()> { 27 let ms: i32 = timeout 28 .as_secs() 29 .checked_mul(1000) 30 .and_then(|t| t.checked_add(timeout.subsec_millis().into())) 31 .and_then(|t| t.try_into().ok()) 32 .expect("too big"); 33 self.db.borrow_mut().busy_timeout(ms) 34 } 35 36 /// Register a callback to handle `SQLITE_BUSY` errors. 37 /// 38 /// If the busy callback is `None`, then `SQLITE_BUSY` is returned 39 /// immediately upon encountering the lock. The argument to the busy 40 /// handler callback is the number of times that the 41 /// busy handler has been invoked previously for the 42 /// same locking event. If the busy callback returns `false`, then no 43 /// additional attempts are made to access the 44 /// database and `SQLITE_BUSY` is returned to the 45 /// application. If the callback returns `true`, then another attempt 46 /// is made to access the database and the cycle repeats. 47 /// 48 /// There can only be a single busy handler defined for each database 49 /// connection. Setting a new busy handler clears any previously set 50 /// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout) 51 /// or evaluating `PRAGMA busy_timeout=N` will change the busy handler 52 /// and thus clear any previously set busy handler. 53 /// 54 /// Newly created connections default to a 55 /// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout 56 /// of 5000ms, although this is subject to change. busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()>57 pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> { 58 unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int { 59 let handler_fn: fn(i32) -> bool = mem::transmute(p_arg); 60 c_int::from(catch_unwind(|| handler_fn(count)).unwrap_or_default()) 61 } 62 let c = self.db.borrow_mut(); 63 let r = match callback { 64 Some(f) => unsafe { 65 ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void) 66 }, 67 None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) }, 68 }; 69 c.decode_result(r) 70 } 71 } 72 73 impl InnerConnection { 74 #[inline] busy_timeout(&mut self, timeout: c_int) -> Result<()>75 fn busy_timeout(&mut self, timeout: c_int) -> Result<()> { 76 let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) }; 77 self.decode_result(r) 78 } 79 } 80 81 #[cfg(test)] 82 mod test { 83 use crate::{Connection, ErrorCode, Result, TransactionBehavior}; 84 use std::sync::atomic::{AtomicBool, Ordering}; 85 86 #[test] test_default_busy() -> Result<()>87 fn test_default_busy() -> Result<()> { 88 let temp_dir = tempfile::tempdir().unwrap(); 89 let path = temp_dir.path().join("test.db3"); 90 91 let mut db1 = Connection::open(&path)?; 92 let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?; 93 let db2 = Connection::open(&path)?; 94 let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!()); 95 assert_eq!( 96 r.unwrap_err().sqlite_error_code(), 97 Some(ErrorCode::DatabaseBusy) 98 ); 99 tx1.rollback() 100 } 101 102 #[test] test_busy_handler() -> Result<()>103 fn test_busy_handler() -> Result<()> { 104 static CALLED: AtomicBool = AtomicBool::new(false); 105 fn busy_handler(n: i32) -> bool { 106 if n > 2 { 107 false 108 } else { 109 CALLED.swap(true, Ordering::Relaxed) 110 } 111 } 112 113 let temp_dir = tempfile::tempdir().unwrap(); 114 let path = temp_dir.path().join("busy-handler.db3"); 115 116 let db1 = Connection::open(&path)?; 117 db1.execute_batch("CREATE TABLE IF NOT EXISTS t(a)")?; 118 let db2 = Connection::open(&path)?; 119 db2.busy_handler(Some(busy_handler))?; 120 db1.execute_batch("BEGIN EXCLUSIVE")?; 121 let err = db2.prepare("SELECT * FROM t").unwrap_err(); 122 assert_eq!(err.sqlite_error_code(), Some(ErrorCode::DatabaseBusy)); 123 assert!(CALLED.load(Ordering::Relaxed)); 124 db1.busy_handler(None)?; 125 Ok(()) 126 } 127 } 128