1 ///! Busy handler (when the database is locked) 2 use std::convert::TryInto; 3 use std::mem; 4 use std::os::raw::{c_int, c_void}; 5 use std::panic::catch_unwind; 6 use std::ptr; 7 use std::time::Duration; 8 9 use crate::ffi; 10 use crate::{Connection, InnerConnection, Result}; 11 12 impl Connection { 13 /// Set a busy handler that sleeps for a specified amount of time when a 14 /// table is locked. The handler will sleep multiple times until at 15 /// least "ms" milliseconds of sleeping have accumulated. 16 /// 17 /// Calling this routine with an argument equal to zero turns off all busy 18 /// handlers. 19 /// 20 /// There can only be a single busy handler for a particular database 21 /// connection at any given moment. If another busy handler was defined 22 /// (using `busy_handler`) prior to calling this routine, that other 23 /// busy handler is cleared. busy_timeout(&self, timeout: Duration) -> Result<()>24 pub fn busy_timeout(&self, timeout: Duration) -> Result<()> { 25 let ms: i32 = timeout 26 .as_secs() 27 .checked_mul(1000) 28 .and_then(|t| t.checked_add(timeout.subsec_millis().into())) 29 .and_then(|t| t.try_into().ok()) 30 .expect("too big"); 31 self.db.borrow_mut().busy_timeout(ms) 32 } 33 34 /// Register a callback to handle `SQLITE_BUSY` errors. 35 /// 36 /// If the busy callback is `None`, then `SQLITE_BUSY is returned 37 /// immediately upon encountering the lock.` The argument to the busy 38 /// handler callback is the number of times that the 39 /// busy handler has been invoked previously for the 40 /// same locking event. If the busy callback returns `false`, then no 41 /// additional attempts are made to access the 42 /// database and `SQLITE_BUSY` is returned to the 43 /// application. If the callback returns `true`, then another attempt 44 /// is made to access the database and the cycle repeats. 45 /// 46 /// There can only be a single busy handler defined for each database 47 /// connection. Setting a new busy handler clears any previously set 48 /// handler. Note that calling `busy_timeout()` or evaluating `PRAGMA 49 /// busy_timeout=N` will change the busy handler and thus 50 /// clear any previously set busy handler. busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()>51 pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> { 52 unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int { 53 let handler_fn: fn(i32) -> bool = mem::transmute(p_arg); 54 if let Ok(true) = catch_unwind(|| handler_fn(count)) { 55 1 56 } else { 57 0 58 } 59 } 60 let mut c = self.db.borrow_mut(); 61 let r = match callback { 62 Some(f) => unsafe { 63 ffi::sqlite3_busy_handler(c.db(), Some(busy_handler_callback), f as *mut c_void) 64 }, 65 None => unsafe { ffi::sqlite3_busy_handler(c.db(), None, ptr::null_mut()) }, 66 }; 67 c.decode_result(r) 68 } 69 } 70 71 impl InnerConnection { busy_timeout(&mut self, timeout: c_int) -> Result<()>72 fn busy_timeout(&mut self, timeout: c_int) -> Result<()> { 73 let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) }; 74 self.decode_result(r) 75 } 76 } 77 78 #[cfg(test)] 79 mod test { 80 use std::sync::atomic::{AtomicBool, Ordering}; 81 use std::sync::mpsc::sync_channel; 82 use std::thread; 83 use std::time::Duration; 84 85 use crate::{Connection, Error, ErrorCode, Result, TransactionBehavior, NO_PARAMS}; 86 87 #[test] test_default_busy()88 fn test_default_busy() { 89 let temp_dir = tempfile::tempdir().unwrap(); 90 let path = temp_dir.path().join("test.db3"); 91 92 let mut db1 = Connection::open(&path).unwrap(); 93 let tx1 = db1 94 .transaction_with_behavior(TransactionBehavior::Exclusive) 95 .unwrap(); 96 let db2 = Connection::open(&path).unwrap(); 97 let r: Result<()> = db2.query_row("PRAGMA schema_version", NO_PARAMS, |_| unreachable!()); 98 match r.unwrap_err() { 99 Error::SqliteFailure(err, _) => { 100 assert_eq!(err.code, ErrorCode::DatabaseBusy); 101 } 102 err => panic!("Unexpected error {}", err), 103 } 104 tx1.rollback().unwrap(); 105 } 106 107 #[test] 108 #[ignore] // FIXME: unstable test_busy_timeout()109 fn test_busy_timeout() { 110 let temp_dir = tempfile::tempdir().unwrap(); 111 let path = temp_dir.path().join("test.db3"); 112 113 let db2 = Connection::open(&path).unwrap(); 114 db2.busy_timeout(Duration::from_secs(1)).unwrap(); 115 116 let (rx, tx) = sync_channel(0); 117 let child = thread::spawn(move || { 118 let mut db1 = Connection::open(&path).unwrap(); 119 let tx1 = db1 120 .transaction_with_behavior(TransactionBehavior::Exclusive) 121 .unwrap(); 122 rx.send(1).unwrap(); 123 thread::sleep(Duration::from_millis(100)); 124 tx1.rollback().unwrap(); 125 }); 126 127 assert_eq!(tx.recv().unwrap(), 1); 128 let _ = db2 129 .query_row("PRAGMA schema_version", NO_PARAMS, |row| { 130 row.get::<_, i32>(0) 131 }) 132 .expect("unexpected error"); 133 134 child.join().unwrap(); 135 } 136 137 #[test] 138 #[ignore] // FIXME: unstable test_busy_handler()139 fn test_busy_handler() { 140 lazy_static::lazy_static! { 141 static ref CALLED: AtomicBool = AtomicBool::new(false); 142 } 143 fn busy_handler(_: i32) -> bool { 144 CALLED.store(true, Ordering::Relaxed); 145 thread::sleep(Duration::from_millis(100)); 146 true 147 } 148 149 let temp_dir = tempfile::tempdir().unwrap(); 150 let path = temp_dir.path().join("test.db3"); 151 152 let db2 = Connection::open(&path).unwrap(); 153 db2.busy_handler(Some(busy_handler)).unwrap(); 154 155 let (rx, tx) = sync_channel(0); 156 let child = thread::spawn(move || { 157 let mut db1 = Connection::open(&path).unwrap(); 158 let tx1 = db1 159 .transaction_with_behavior(TransactionBehavior::Exclusive) 160 .unwrap(); 161 rx.send(1).unwrap(); 162 thread::sleep(Duration::from_millis(100)); 163 tx1.rollback().unwrap(); 164 }); 165 166 assert_eq!(tx.recv().unwrap(), 1); 167 let _ = db2 168 .query_row("PRAGMA schema_version", NO_PARAMS, |row| { 169 row.get::<_, i32>(0) 170 }) 171 .expect("unexpected error"); 172 assert_eq!(CALLED.load(Ordering::Relaxed), true); 173 174 child.join().unwrap(); 175 } 176 } 177