#[cfg(test)]
use std::collections::HashMap;
#[cfg(test)]
use std::path::PathBuf;
use std::sync::atomic::Ordering;
#[cfg(test)]
use std::sync::{Mutex, OnceLock};
use crate::pool::ConnectionPool;
#[cfg(test)]
use super::checkpoint_db_key;
use super::{note_checkpoint_observed, record_checkpoint_run_result, LAST_WAL_PAGES};
pub(super) fn log_backfill_gap(pool: &ConnectionPool, conn: &rusqlite::Connection) {
match query_backfill_gap(conn) {
Ok(observation) => {
record_checkpoint_run_result(
pool,
Some((
observation.busy,
observation.log_frames,
observation.checkpointed_frames,
)),
);
if observed_wal_pages(observation).is_ok() {
tracing::warn!(target: "khive_db::checkpoint",
busy = observation.busy,
wal_log_frames = observation.log_frames,
wal_checkpointed_frames = observation.checkpointed_frames,
backfill_gap_frames = observation
.log_frames
.saturating_sub(observation.checkpointed_frames)
.max(0),
"ADR-091 Plank C: WAL backfill gap after TRUNCATE no-progress"
);
} else {
tracing::warn!(target: "khive_db::checkpoint",
busy = observation.busy,
wal_log_frames = observation.log_frames,
wal_checkpointed_frames = observation.checkpointed_frames,
"ADR-091 Plank C: WAL backfill gap unavailable after TRUNCATE"
);
}
}
Err(e) => {
record_checkpoint_run_result(pool, None);
tracing::warn!(target: "khive_db::checkpoint",
error = %e,
"ADR-091 Plank C: failed to query WAL backfill gap"
);
}
}
}
pub(super) fn query_backfill_gap(
conn: &rusqlite::Connection,
) -> rusqlite::Result<RawCheckpointObservation> {
conn.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |row| {
Ok(RawCheckpointObservation {
busy: row.get(0)?,
log_frames: row.get(1)?,
checkpointed_frames: row.get(2)?,
})
})
}
pub(super) fn crossing_warn(now_above: bool, was_above: &mut bool) -> bool {
let fire = now_above && !*was_above;
*was_above = now_above;
fire
}
#[derive(Debug, Clone, Copy)]
pub(super) struct RawCheckpointObservation {
pub(super) busy: i64,
pub(super) log_frames: i64,
pub(super) checkpointed_frames: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CheckpointUnavailableReason {
Busy,
InconsistentFrames,
}
pub(super) fn observed_wal_pages(
observation: RawCheckpointObservation,
) -> Result<u64, CheckpointUnavailableReason> {
if observation.busy != 0 {
return Err(CheckpointUnavailableReason::Busy);
}
if observation.log_frames == -1 && observation.checkpointed_frames == -1 {
return Ok(0);
}
if observation.log_frames >= 0
&& observation.checkpointed_frames >= 0
&& observation.checkpointed_frames <= observation.log_frames
{
Ok(observation.log_frames as u64)
} else {
Err(CheckpointUnavailableReason::InconsistentFrames)
}
}
pub(super) fn query_checkpoint_observation(
conn: &rusqlite::Connection,
) -> rusqlite::Result<RawCheckpointObservation> {
conn.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |row| {
Ok(RawCheckpointObservation {
busy: row.get(0)?,
log_frames: row.get(1)?,
checkpointed_frames: row.get(2)?,
})
})
}
pub(super) fn query_routine_checkpoint_observation(
pool: &ConnectionPool,
conn: &rusqlite::Connection,
) -> rusqlite::Result<RawCheckpointObservation> {
#[cfg(test)]
if let Some(row) = test_take_passive_row(pool) {
return Ok(row);
}
#[cfg(not(test))]
let _ = pool;
query_checkpoint_observation(conn)
}
#[cfg(test)]
static TEST_PASSIVE_ROWS: OnceLock<Mutex<HashMap<Option<PathBuf>, RawCheckpointObservation>>> =
OnceLock::new();
#[cfg(test)]
fn test_passive_rows() -> &'static Mutex<HashMap<Option<PathBuf>, RawCheckpointObservation>> {
TEST_PASSIVE_ROWS.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(test)]
pub(super) fn test_arm_passive_row(pool: &ConnectionPool, row: RawCheckpointObservation) {
test_passive_rows()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(checkpoint_db_key(pool), row);
}
#[cfg(test)]
pub(super) fn test_take_passive_row(pool: &ConnectionPool) -> Option<RawCheckpointObservation> {
test_passive_rows()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.remove(&checkpoint_db_key(pool))
}
pub(super) fn query_truncate_observation(
conn: &rusqlite::Connection,
) -> rusqlite::Result<RawCheckpointObservation> {
conn.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |row| {
Ok(RawCheckpointObservation {
busy: row.get(0)?,
log_frames: row.get(1)?,
checkpointed_frames: row.get(2)?,
})
})
}
pub(super) fn query_wal_pages(pool: &ConnectionPool, conn: &rusqlite::Connection) -> Option<u64> {
let observation = query_checkpoint_observation(conn);
record_checkpoint_run_result(
pool,
observation.as_ref().ok().map(|observation| {
(
observation.busy,
observation.log_frames,
observation.checkpointed_frames,
)
}),
);
let pages = observation
.ok()
.and_then(|row| observed_wal_pages(row).ok());
if let Some(pages) = pages {
LAST_WAL_PAGES.store(pages, Ordering::Relaxed);
note_checkpoint_observed(pages);
}
pages
}