use std::cell::Cell;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use khive_storage::{error::StorageError, StorageCapability};
use rusqlite::Connection;
use crate::error::SqliteError;
use super::{
close_connection_quietly, pool_exhausted_error, restore_shared_reader_state, ConnectionPool,
PoolConfig,
};
#[derive(Debug)]
pub(super) struct PooledReader {
pub(super) conn: Connection,
pub(super) opened_at: Instant,
pub(super) checkouts: u64,
}
impl PooledReader {
pub(super) fn past_limit(&self, config: &PoolConfig, now: Instant) -> bool {
now.saturating_duration_since(self.opened_at) > config.reader_max_age
|| self.checkouts > config.reader_max_ops
}
}
pub(super) enum ReaderLease<'pool> {
Pooled(Box<PooledReader>),
Shared(parking_lot::MutexGuard<'pool, Connection>),
}
pub struct ReaderRow<'row, 'statement> {
row: &'row rusqlite::Row<'statement>,
}
impl ReaderRow<'_, '_> {
pub fn get<I: rusqlite::RowIndex, T: rusqlite::types::FromSql>(
&self,
index: I,
) -> rusqlite::Result<T> {
self.row.get(index)
}
pub fn get_ref<I: rusqlite::RowIndex>(
&self,
index: I,
) -> rusqlite::Result<rusqlite::types::ValueRef<'_>> {
self.row.get_ref(index)
}
}
struct ReaderQueryInProgress<'a>(&'a Cell<bool>);
impl Drop for ReaderQueryInProgress<'_> {
fn drop(&mut self) {
self.0.set(false);
}
}
pub(crate) struct ReaderAdmission {
pub(super) slot: tokio::sync::OwnedSemaphorePermit,
pub(super) started: Instant,
}
pub struct ReaderGuard<'pool> {
pub(super) lease: Option<ReaderLease<'pool>>,
pub(super) admission_slot: Option<tokio::sync::OwnedSemaphorePermit>,
pub(super) pool: &'pool ConnectionPool,
pub(super) reusable: Cell<bool>,
pub(super) query_in_progress: Cell<bool>,
pub(super) checked_out_at: Instant,
pub(super) dirty: Cell<bool>,
pub(super) operation: Option<&'static str>,
}
impl<'pool> ReaderGuard<'pool> {
pub(crate) fn conn(&self) -> &Connection {
match self
.lease
.as_ref()
.expect("reader guard missing connection")
{
ReaderLease::Pooled(reader) => &reader.conn,
ReaderLease::Shared(guard) => guard,
}
}
pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, SqliteError>
where
P: rusqlite::Params,
F: FnOnce(&ReaderRow<'_, '_>) -> rusqlite::Result<T>,
{
crate::sql_bridge::reader_capability_admits(sql).map_err(SqliteError::InvalidData)?;
if !self.reusable.get() {
return Err(SqliteError::InvalidData(
"reader lease is quarantined after failed read cleanup".into(),
));
}
if self.query_in_progress.replace(true) {
return Err(SqliteError::InvalidData(
"reader lease is already executing a query".into(),
));
}
let _in_progress = ReaderQueryInProgress(&self.query_in_progress);
self.mark_dirty();
crate::read_cancellation::run_borrowed_reader(self, |conn, admission| {
conn.query_row(sql, params, |row| {
if !admission.admits() {
return Err(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_INTERRUPT),
Some("request stopped before the mapper".into()),
));
}
f(&ReaderRow { row })
})
.map_err(|error| {
StorageError::driver(StorageCapability::Sql, "reader_guard.query_row", error)
})
})
.map_err(|error| {
self.pool.record_reader_query_error(&error);
match error {
StorageError::Driver {
capability,
operation,
source,
} => match source.downcast::<rusqlite::Error>() {
Ok(error) => SqliteError::Rusqlite(*error),
Err(source) => SqliteError::RequestReadStopped(StorageError::Driver {
capability,
operation,
source,
}),
},
other => SqliteError::RequestReadStopped(other),
}
})
}
pub(crate) fn discard(&self) {
self.reusable.set(false);
}
pub(crate) fn mark_dirty(&self) {
self.dirty.set(true);
}
pub(crate) fn label_operation(&mut self, operation: &'static str) {
self.operation = Some(operation);
}
}
impl<'pool> Drop for ReaderGuard<'pool> {
fn drop(&mut self) {
let Some(lease) = self.lease.take() else {
return;
};
match lease {
ReaderLease::Pooled(conn) if self.reusable.get() => {
self.pool.return_reader(conn, self.dirty.get())
}
ReaderLease::Pooled(conn) => {
close_connection_quietly(conn.conn);
self.pool.replace_discarded_reader_slot();
}
ReaderLease::Shared(guard) if !self.reusable.get() => {
self.pool.retire_pooled_writer(&guard);
}
ReaderLease::Shared(guard) => {
if self.dirty.get() && !restore_shared_reader_state(&guard, &self.pool.config) {
self.pool.retire_pooled_writer(&guard);
}
}
}
drop(self.admission_slot.take());
self.pool
.reader_acquisition_counters
.record_checkout_completed(self.checked_out_at.elapsed(), self.operation);
self.pool.finish_reader_checkout(self.checked_out_at);
}
}
pub(crate) struct SharedReaderTransactionGuard {
conn: parking_lot::ArcMutexGuard<parking_lot::RawMutex, Connection>,
admission_slot: Option<tokio::sync::OwnedSemaphorePermit>,
pool: Arc<ConnectionPool>,
checked_out_at: Instant,
poison: Cell<bool>,
}
impl SharedReaderTransactionGuard {
pub(crate) fn conn(&self) -> &Connection {
&self.conn
}
pub(crate) fn poison(&self) {
self.poison.set(true);
}
}
impl Drop for SharedReaderTransactionGuard {
fn drop(&mut self) {
let mut restored = !self.poison.get();
if restored && !self.conn.is_autocommit() {
restored = self.conn.execute_batch("ROLLBACK").is_ok() && self.conn.is_autocommit();
}
if restored {
restored = restore_shared_reader_state(&self.conn, &self.pool.config);
}
if !restored {
self.pool.retire_pooled_writer(&self.conn);
}
drop(self.admission_slot.take());
self.pool
.reader_acquisition_counters
.record_checkout_completed(
self.checked_out_at.elapsed(),
Some("explicit_sql_read_transaction"),
);
self.pool.finish_reader_checkout(self.checked_out_at);
}
}
impl ConnectionPool {
pub(crate) fn checkout_shared_reader_transaction(
self: &Arc<Self>,
should_stop: impl Fn() -> bool,
) -> Result<Option<SharedReaderTransactionGuard>, SqliteError> {
debug_assert_eq!(
self.max_readers, 0,
"the owned shared-reader-transaction guard exists only for the degraded, \
single-connection backend"
);
self.ensure_pooled_writer_active()?;
let started = Instant::now();
let admission_slot = loop {
if should_stop() {
return Ok(None);
}
match Arc::clone(&self.sql_bridge_reader_slots).try_acquire_owned() {
Ok(slot) => break slot,
Err(tokio::sync::TryAcquireError::Closed) => {
return Err(SqliteError::InvalidData(
"reader admission semaphore is closed".to_string(),
));
}
Err(tokio::sync::TryAcquireError::NoPermits) => {}
}
if started.elapsed() >= self.config.checkout_timeout {
self.reader_acquisition_counters.record_checkout_timeout();
return Err(pool_exhausted_error(
self.config.checkout_timeout,
self.max_readers,
));
}
thread::yield_now();
};
loop {
if should_stop() {
return Ok(None);
}
let remaining = self
.config
.checkout_timeout
.saturating_sub(started.elapsed());
if remaining.is_zero() {
self.reader_acquisition_counters.record_checkout_timeout();
return Err(pool_exhausted_error(
self.config.checkout_timeout,
self.max_readers,
));
}
if let Some(conn) = self
.writer
.try_lock_arc_for(remaining.min(Duration::from_millis(2)))
{
self.ensure_pooled_writer_active()?;
self.reader_acquisition_counters.record_pooled_checkout();
return Ok(Some(SharedReaderTransactionGuard {
conn,
admission_slot: Some(admission_slot),
pool: Arc::clone(self),
checked_out_at: self.begin_reader_checkout(),
poison: Cell::new(false),
}));
}
}
}
}