1use std::ffi::{c_int, c_void};
3use std::mem;
4use std::panic::catch_unwind;
5use std::ptr;
6use std::time::Duration;
7
8use crate::ffi;
9use crate::{Connection, InnerConnection, Result};
10
11impl Connection {
12 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 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 c.decode_result(unsafe {
64 ffi::sqlite3_busy_handler(
65 c.db(),
66 callback.as_ref().map(|_| busy_handler_callback as _),
67 callback.map_or_else(ptr::null_mut, |f| f as *mut c_void),
68 )
69 })
70 }
71}
72
73impl InnerConnection {
74 #[inline]
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)]
82mod test {
83 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
84 use wasm_bindgen_test::wasm_bindgen_test as test;
85
86 use crate::{Connection, ErrorCode, Result, TransactionBehavior};
87 use std::sync::atomic::{AtomicBool, Ordering};
88
89 #[cfg_attr(
90 all(target_family = "wasm", target_os = "unknown"),
91 ignore = "no filesystem on this platform"
92 )]
93 #[test]
94 fn test_default_busy() -> Result<()> {
95 let temp_dir = tempfile::tempdir().unwrap();
96 let path = temp_dir.path().join("test.db3");
97
98 let mut db1 = Connection::open(&path)?;
99 let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
100 let db2 = Connection::open(&path)?;
101 let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
102 assert_eq!(
103 r.unwrap_err().sqlite_error_code(),
104 Some(ErrorCode::DatabaseBusy)
105 );
106 tx1.rollback()
107 }
108
109 #[cfg_attr(
110 all(target_family = "wasm", target_os = "unknown"),
111 ignore = "no filesystem on this platform"
112 )]
113 #[test]
114 fn test_busy_handler() -> Result<()> {
115 static CALLED: AtomicBool = AtomicBool::new(false);
116 fn busy_handler(n: i32) -> bool {
117 if n > 2 {
118 false
119 } else {
120 CALLED.swap(true, Ordering::Relaxed)
121 }
122 }
123
124 let temp_dir = tempfile::tempdir().unwrap();
125 let path = temp_dir.path().join("busy-handler.db3");
126
127 let db1 = Connection::open(&path)?;
128 db1.execute_batch("CREATE TABLE IF NOT EXISTS t(a)")?;
129 let db2 = Connection::open(&path)?;
130 db2.busy_handler(Some(busy_handler))?;
131 db1.execute_batch("BEGIN EXCLUSIVE")?;
132 let err = db2.prepare("SELECT * FROM t").unwrap_err();
133 assert_eq!(err.sqlite_error_code(), Some(ErrorCode::DatabaseBusy));
134 assert!(CALLED.load(Ordering::Relaxed));
135 db1.busy_handler(None)?;
136 Ok(())
137 }
138}