story-kit: merge 260_refactor_upgrade_libsqlite3_sys
This commit is contained in:
138
vendor/rusqlite/src/busy.rs
vendored
Normal file
138
vendor/rusqlite/src/busy.rs
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
//! Busy handler (when the database is locked)
|
||||
use std::ffi::{c_int, c_void};
|
||||
use std::mem;
|
||||
use std::panic::catch_unwind;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::ffi;
|
||||
use crate::{Connection, InnerConnection, Result};
|
||||
|
||||
impl Connection {
|
||||
/// Set a busy handler that sleeps for a specified amount of time when a
|
||||
/// table is locked. The handler will sleep multiple times until at
|
||||
/// least "ms" milliseconds of sleeping have accumulated.
|
||||
///
|
||||
/// Calling this routine with an argument equal to zero turns off all busy
|
||||
/// handlers.
|
||||
///
|
||||
/// There can only be a single busy handler for a particular database
|
||||
/// connection at any given moment. If another busy handler was defined
|
||||
/// (using [`busy_handler`](Connection::busy_handler)) prior to calling this
|
||||
/// routine, that other busy handler is cleared.
|
||||
///
|
||||
/// Newly created connections currently have a default busy timeout of
|
||||
/// 5000ms, but this may be subject to change.
|
||||
pub fn busy_timeout(&self, timeout: Duration) -> Result<()> {
|
||||
let ms: i32 = timeout
|
||||
.as_secs()
|
||||
.checked_mul(1000)
|
||||
.and_then(|t| t.checked_add(timeout.subsec_millis().into()))
|
||||
.and_then(|t| t.try_into().ok())
|
||||
.expect("too big");
|
||||
self.db.borrow_mut().busy_timeout(ms)
|
||||
}
|
||||
|
||||
/// Register a callback to handle `SQLITE_BUSY` errors.
|
||||
///
|
||||
/// If the busy callback is `None`, then `SQLITE_BUSY` is returned
|
||||
/// immediately upon encountering the lock. The argument to the busy
|
||||
/// handler callback is the number of times that the
|
||||
/// busy handler has been invoked previously for the
|
||||
/// same locking event. If the busy callback returns `false`, then no
|
||||
/// additional attempts are made to access the
|
||||
/// database and `SQLITE_BUSY` is returned to the
|
||||
/// application. If the callback returns `true`, then another attempt
|
||||
/// is made to access the database and the cycle repeats.
|
||||
///
|
||||
/// There can only be a single busy handler defined for each database
|
||||
/// connection. Setting a new busy handler clears any previously set
|
||||
/// handler. Note that calling [`busy_timeout()`](Connection::busy_timeout)
|
||||
/// or evaluating `PRAGMA busy_timeout=N` will change the busy handler
|
||||
/// and thus clear any previously set busy handler.
|
||||
///
|
||||
/// Newly created connections default to a
|
||||
/// [`busy_timeout()`](Connection::busy_timeout) handler with a timeout
|
||||
/// of 5000ms, although this is subject to change.
|
||||
pub fn busy_handler(&self, callback: Option<fn(i32) -> bool>) -> Result<()> {
|
||||
unsafe extern "C" fn busy_handler_callback(p_arg: *mut c_void, count: c_int) -> c_int {
|
||||
let handler_fn: fn(i32) -> bool = mem::transmute(p_arg);
|
||||
c_int::from(catch_unwind(|| handler_fn(count)).unwrap_or_default())
|
||||
}
|
||||
let c = self.db.borrow_mut();
|
||||
c.decode_result(unsafe {
|
||||
ffi::sqlite3_busy_handler(
|
||||
c.db(),
|
||||
callback.as_ref().map(|_| busy_handler_callback as _),
|
||||
callback.map_or_else(ptr::null_mut, |f| f as *mut c_void),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InnerConnection {
|
||||
#[inline]
|
||||
fn busy_timeout(&mut self, timeout: c_int) -> Result<()> {
|
||||
let r = unsafe { ffi::sqlite3_busy_timeout(self.db, timeout) };
|
||||
self.decode_result(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use crate::{Connection, ErrorCode, Result, TransactionBehavior};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no filesystem on this platform"
|
||||
)]
|
||||
#[test]
|
||||
fn test_default_busy() -> Result<()> {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let path = temp_dir.path().join("test.db3");
|
||||
|
||||
let mut db1 = Connection::open(&path)?;
|
||||
let tx1 = db1.transaction_with_behavior(TransactionBehavior::Exclusive)?;
|
||||
let db2 = Connection::open(&path)?;
|
||||
let r: Result<()> = db2.query_row("PRAGMA schema_version", [], |_| unreachable!());
|
||||
assert_eq!(
|
||||
r.unwrap_err().sqlite_error_code(),
|
||||
Some(ErrorCode::DatabaseBusy)
|
||||
);
|
||||
tx1.rollback()
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
all(target_family = "wasm", target_os = "unknown"),
|
||||
ignore = "no filesystem on this platform"
|
||||
)]
|
||||
#[test]
|
||||
fn test_busy_handler() -> Result<()> {
|
||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
||||
fn busy_handler(n: i32) -> bool {
|
||||
if n > 2 {
|
||||
false
|
||||
} else {
|
||||
CALLED.swap(true, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let path = temp_dir.path().join("busy-handler.db3");
|
||||
|
||||
let db1 = Connection::open(&path)?;
|
||||
db1.execute_batch("CREATE TABLE IF NOT EXISTS t(a)")?;
|
||||
let db2 = Connection::open(&path)?;
|
||||
db2.busy_handler(Some(busy_handler))?;
|
||||
db1.execute_batch("BEGIN EXCLUSIVE")?;
|
||||
let err = db2.prepare("SELECT * FROM t").unwrap_err();
|
||||
assert_eq!(err.sqlite_error_code(), Some(ErrorCode::DatabaseBusy));
|
||||
assert!(CALLED.load(Ordering::Relaxed));
|
||||
db1.busy_handler(None)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user