use std::{thread, time::Duration};
use rusqlite::ErrorCode;
use crate::storage::StorageError;
const SQLITE_TRANSIENT_RETRY_DELAYS_MS: [u64; 6] = [10, 30, 90, 270, 810, 1620];
pub(in crate::storage::sqlite) fn retry_sqlite_transient<T>(
mut operation: impl FnMut() -> Result<T, StorageError>,
) -> Result<T, StorageError> {
for delay_ms in SQLITE_TRANSIENT_RETRY_DELAYS_MS {
match operation() {
Ok(value) => return Ok(value),
Err(error) if sqlite_transient_error_is_retryable(&error) => {
thread::sleep(Duration::from_millis(delay_ms));
}
Err(error) => return Err(error),
}
}
operation()
}
fn sqlite_transient_error_is_retryable(error: &StorageError) -> bool {
match error {
StorageError::Sqlite(error) => sqlite_error_is_retryable(error),
_ => false,
}
}
fn sqlite_error_is_retryable(error: &rusqlite::Error) -> bool {
match error {
rusqlite::Error::SqliteFailure(failure, _) => {
matches!(
failure.code,
ErrorCode::DatabaseBusy | ErrorCode::DatabaseLocked
) || sqlite_transient_message_is_retryable(error.to_string().as_str())
}
_ => sqlite_transient_message_is_retryable(error.to_string().as_str()),
}
}
fn sqlite_transient_message_is_retryable(message: &str) -> bool {
message.contains("database is locked")
|| message.contains("database table is locked")
|| message.contains("database schema is locked")
|| message.contains("vtable constructor failed")
}
#[cfg(test)]
#[path = "retry_tests.rs"]
mod tests;