use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(test)]
use std::sync::Arc;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
use crate::pool::ConnectionPool;
mod backfill_queries;
mod checkpoint_core;
mod lifecycle;
mod task_loop;
mod walpin_attribution;
mod walpin_sidecar;
use backfill_queries::{
crossing_warn, log_backfill_gap, observed_wal_pages, query_routine_checkpoint_observation,
query_truncate_observation, query_wal_pages, CheckpointUnavailableReason,
RawCheckpointObservation,
};
#[cfg(test)]
use backfill_queries::{
query_backfill_gap, query_checkpoint_observation, test_arm_passive_row, test_take_passive_row,
};
use checkpoint_core::{
checkpoint_once_core, log_tx_registry_oldest_debug, log_tx_registry_oldest_warn,
log_tx_registry_snapshot_warn, log_wal_high_water_warn, CheckpointCoreOutcome,
};
#[cfg(test)]
use checkpoint_core::{
log_truncate_no_progress_warn, maybe_truncate, note_truncate_outcome,
truncate_needs_attribution, truncate_report_test_sync,
};
pub use checkpoint_core::checkpoint_once;
pub use lifecycle::{run_session_sweep_task, CheckpointLifecycleOwner, SweepBackend};
pub use task_loop::run_checkpoint_task;
use lifecycle::{CheckpointConnection, CheckpointLifecycleEmitter, CheckpointPressureEpisode};
#[cfg(test)]
use task_loop::{
checkpoint_outcome_should_emit, observe_checkpoint_pressure_tick,
run_fts_maintenance_off_worker,
};
use walpin_attribution::WalpinAttributionRequest;
#[cfg(unix)]
use walpin_attribution::{capture_walpin_attribution_request, complete_walpin_attribution};
#[cfg(all(test, unix))]
use walpin_attribution::{
log_walpin_sidecar_report, walpin_attribution_test_sync, WalpinAttributionFailure,
WalpinReportFreshness,
};
use walpin_sidecar::{log_tx_age_emission, ReaderCheckoutSweepState, WalpinSidecarState};
#[cfg(unix)]
use walpin_sidecar::{
now_epoch_secs, run_walpin_housekeeping_if_due, CachedWalpinAttribution, WalpinFullScanPlan,
DEFAULT_WALPIN_FULL_SCAN_INTERVAL,
};
mod off_worker;
#[cfg(test)]
mod off_worker_tests;
static LAST_WAL_PAGES: AtomicU64 = AtomicU64::new(u64::MAX);
static TRUNCATE_ATTEMPTS: AtomicU64 = AtomicU64::new(0);
static TRUNCATE_CONSECUTIVE_FAILURES: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_SKIPPED_TICKS: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_CONSECUTIVE_SKIPS: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_LAST_SKIP_WAL_PAGES: AtomicU64 = AtomicU64::new(u64::MAX);
static CHECKPOINT_PRESSURE_ELEVATED_TICKS: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_PRESSURE_EPISODES_STARTED: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_PRESSURE_EPISODES_RECOVERED: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_LIFECYCLE_APPEND_ATTEMPTS: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_LIFECYCLE_APPEND_FAILURES: AtomicU64 = AtomicU64::new(0);
static CHECKPOINT_LIFECYCLE_ENQUEUE_DROPS: AtomicU64 = AtomicU64::new(0);
static READ_TX_MAX_AGE_EVICTIONS: AtomicU64 = AtomicU64::new(0);
mod run_state;
pub use run_state::{
checkpoint_consecutive_skips, checkpoint_last_skip_wal_pages,
checkpoint_lifecycle_append_attempts, checkpoint_lifecycle_append_failures,
checkpoint_lifecycle_enqueue_drops, checkpoint_pressure_elevated_ticks,
checkpoint_pressure_episodes_recovered, checkpoint_pressure_episodes_started,
checkpoint_skipped_ticks, checkpoint_timing, last_observed_wal_pages,
read_tx_max_age_evictions, routine_wal_observation, truncate_attempts,
truncate_consecutive_failures, CheckpointRun, CheckpointTick, CheckpointTiming,
RoutineWalObservation,
};
pub(crate) use run_state::{
checkpoint_run_snapshot, note_read_tx_max_age_eviction, record_checkpoint_run_result,
CheckpointRunStatus, CheckpointRunTaskGuard,
};
use run_state::{
note_checkpoint_observed, note_checkpoint_pressure_observation, note_checkpoint_skipped,
record_checkpoint_timing, record_routine_wal_observation,
};
#[cfg(test)]
pub(crate) use run_state::{checkpoint_run_status, reset_checkpoint_metrics_for_tests};
#[cfg(test)]
use run_state::{
advance_checkpoint_run, advance_checkpoint_run_at, checkpoint_db_key,
checkpoint_db_key_from_path, checkpoint_runs, checkpoint_timings,
};
mod config;
pub(crate) use config::tx_age_thresholds_from_env;
pub use config::{CheckpointConfig, DEFAULT_WARN_SUSTAINED_CYCLES};
mod severity_state;
pub use severity_state::{
CheckpointSeverityEmission, CheckpointSeverityRung, CheckpointSeverityState, TruncateState,
TxAgeEmission, TxAgeRung, TxAgeSweepState,
};
const DEFAULT_SESSION_SWEEP_INTERVAL: Duration = Duration::from_secs(5);
#[derive(Clone, Debug)]
pub struct SessionSweepConfig {
pub interval: Duration,
pub tx_warn_secs: Duration,
pub tx_max_age_secs: Duration,
}
impl Default for SessionSweepConfig {
fn default() -> Self {
Self {
interval: DEFAULT_SESSION_SWEEP_INTERVAL,
tx_warn_secs: Duration::from_secs(30),
tx_max_age_secs: Duration::from_secs(120),
}
}
}
impl SessionSweepConfig {
pub fn from_env() -> Self {
let mut cfg = Self {
interval: session_sweep_interval_from_env(),
..Self::default()
};
(cfg.tx_warn_secs, cfg.tx_max_age_secs) =
tx_age_thresholds_from_env(cfg.tx_warn_secs, cfg.tx_max_age_secs);
cfg
}
}
fn session_sweep_interval_from_env() -> Duration {
let millis = crate::env::env_parse_or("KHIVE_SESSION_SWEEP_INTERVAL_MS", 0_u64);
if millis > 0 {
Duration::from_millis(millis)
} else {
DEFAULT_SESSION_SWEEP_INTERVAL
}
}
#[cfg(test)]
#[path = "checkpoint_tests.rs"]
mod tests;