use super::{
map_rusqlite_err, Arc, ConnectionPool, OwnedSemaphorePermit, Semaphore,
StandaloneReaderPurpose, StorageCapability, StorageError,
};
#[derive(Clone, Copy)]
pub(super) enum SlotTimeoutClass {
Admission,
ReaderContract,
}
pub(super) async fn acquire_reader_handle_slot(
pool: &ConnectionPool,
operation: &'static str,
class: SlotTimeoutClass,
) -> Result<OwnedSemaphorePermit, StorageError> {
let result = acquire_handle_slot(
pool.sql_bridge_reader_slots(),
pool.config().checkout_timeout,
operation,
class,
)
.await;
if matches!(
&result,
Err(StorageError::Timeout { .. } | StorageError::AdmissionTimeout { .. })
) {
pool.record_reader_admission_timeout();
}
result
}
pub(crate) async fn acquire_in_memory_write_unit(
pool: &ConnectionPool,
operation: &'static str,
) -> Result<OwnedSemaphorePermit, StorageError> {
acquire_handle_slot(
pool.sql_bridge_writer_slots(),
pool.config().checkout_timeout,
operation,
SlotTimeoutClass::Admission,
)
.await
}
pub(super) async fn acquire_handle_slot(
slots: Arc<Semaphore>,
timeout: std::time::Duration,
operation: &'static str,
class: SlotTimeoutClass,
) -> Result<OwnedSemaphorePermit, StorageError> {
tokio::time::timeout(timeout, slots.acquire_owned())
.await
.map_err(|_| match class {
SlotTimeoutClass::Admission => StorageError::AdmissionTimeout {
operation: operation.into(),
timeout_ms: u64::try_from(timeout.as_millis()).unwrap_or(u64::MAX),
pool_identity: None,
},
SlotTimeoutClass::ReaderContract => StorageError::Timeout {
operation: operation.into(),
},
})?
.map_err(|error| StorageError::Pool {
operation: operation.into(),
message: error.to_string(),
})
}
pub(super) fn open_standalone_reader(
pool: &ConnectionPool,
) -> Result<rusqlite::Connection, StorageError> {
pool.open_standalone_reader(StandaloneReaderPurpose::ExplicitSqlReadTransaction)
.map_err(|error| StorageError::driver(StorageCapability::Sql, "open_reader", error))
}
#[cfg(test)]
pub(super) fn open_standalone_writer(
pool: &ConnectionPool,
) -> Result<rusqlite::Connection, StorageError> {
let conn = pool
.open_standalone_writer()
.map_err(|e| e.into_storage_error(StorageCapability::Sql, "open_writer"))?;
configure_standalone_writer(pool, conn)
}
fn open_admitted_standalone_writer(
pool: &ConnectionPool,
) -> Result<rusqlite::Connection, StorageError> {
let conn = pool
.open_standalone_writer_for_admitted_operation()
.map_err(|e| e.into_storage_error(StorageCapability::Sql, "open_writer"))?;
configure_standalone_writer(pool, conn)
}
fn configure_standalone_writer(
pool: &ConnectionPool,
conn: rusqlite::Connection,
) -> Result<rusqlite::Connection, StorageError> {
let config = pool.config();
conn.busy_timeout(config.busy_timeout)
.map_err(|e| map_rusqlite_err(e, "open_writer"))?;
conn.pragma_update(None, "cache_size", crate::pool::sqlite_cache_size_pragma())
.map_err(|e| map_rusqlite_err(e, "open_writer"))?;
conn.pragma_update(None, "mmap_size", crate::pool::sqlite_mmap_size_pragma())
.map_err(|e| map_rusqlite_err(e, "open_writer"))?;
Ok(conn)
}
pub(super) async fn open_standalone_on_blocking<F>(
pool: Arc<ConnectionPool>,
slot: OwnedSemaphorePermit,
operation: &'static str,
open: F,
) -> khive_storage::types::StorageResult<(rusqlite::Connection, OwnedSemaphorePermit)>
where
F: FnOnce(&ConnectionPool) -> Result<rusqlite::Connection, StorageError> + Send + 'static,
{
tokio::task::spawn_blocking(move || open(&pool).map(|conn| (conn, slot)))
.await
.map_err(|e| StorageError::driver(StorageCapability::Sql, operation, e))?
}
pub(super) async fn open_standalone_reader_on_blocking(
pool: Arc<ConnectionPool>,
slot: OwnedSemaphorePermit,
) -> khive_storage::types::StorageResult<(rusqlite::Connection, OwnedSemaphorePermit)> {
open_standalone_on_blocking(pool, slot, "open_reader", open_standalone_reader).await
}
pub(super) async fn open_standalone_writer_on_blocking(
pool: Arc<ConnectionPool>,
slot: OwnedSemaphorePermit,
) -> khive_storage::types::StorageResult<(rusqlite::Connection, OwnedSemaphorePermit)> {
open_standalone_on_blocking(pool, slot, "open_writer", open_admitted_standalone_writer).await
}