use crate::{checkpoint, checkpoint_restart, wal_path, CheckpointStatus, Result};
use rusqlite::Connection;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, MutexGuard};
use std::time::{Duration, Instant};
const MIB: u64 = 1024 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WalCheckpointPolicy {
pub passive_above_bytes: u64,
pub restart_above_bytes: u64,
pub quiet_for: Duration,
}
impl Default for WalCheckpointPolicy {
fn default() -> Self {
Self {
passive_above_bytes: 16 * MIB,
restart_above_bytes: 256 * MIB,
quiet_for: Duration::from_secs(30),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WalCheckpointMode {
Passive,
Restart,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WalCheckpointRun {
pub mode: WalCheckpointMode,
pub wal_bytes: u64,
pub status: CheckpointStatus,
}
#[derive(Debug)]
pub struct WalCheckpointJob {
wal: PathBuf,
policy: WalCheckpointPolicy,
last_activity: Mutex<Instant>,
running: AtomicBool,
}
impl WalCheckpointJob {
pub fn new(db: impl AsRef<Path>, policy: WalCheckpointPolicy) -> Result<Self> {
Ok(Self {
wal: wal_path(db.as_ref())?,
policy,
last_activity: Mutex::new(Instant::now()),
running: AtomicBool::new(false),
})
}
pub fn policy(&self) -> WalCheckpointPolicy {
self.policy
}
pub fn note_activity(&self) {
self.note_activity_at(Instant::now());
}
pub fn note_activity_at(&self, now: Instant) {
let mut last = lock_recover(&self.last_activity);
if now > *last {
*last = now;
}
}
pub fn tick(&self, conn: &Connection) -> Result<Option<WalCheckpointRun>> {
self.tick_at(conn, Instant::now())
}
pub fn tick_at(&self, conn: &Connection, now: Instant) -> Result<Option<WalCheckpointRun>> {
if self
.running
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return Ok(None);
}
let _flight = Flight(&self.running);
let wal_bytes = std::fs::metadata(&self.wal).map_or(0, |m| m.len());
if wal_bytes == 0 {
return Ok(None);
}
let idle = now.saturating_duration_since(*lock_recover(&self.last_activity));
let restart_due =
wal_bytes >= self.policy.restart_above_bytes && idle >= self.policy.quiet_for;
let mode = if restart_due {
WalCheckpointMode::Restart
} else if wal_bytes >= self.policy.passive_above_bytes {
WalCheckpointMode::Passive
} else {
return Ok(None);
};
let status = match mode {
WalCheckpointMode::Restart => checkpoint_restart(conn)?,
WalCheckpointMode::Passive => checkpoint(conn, "PRAGMA wal_checkpoint(PASSIVE)")?,
};
Ok(Some(WalCheckpointRun {
mode,
wal_bytes,
status,
}))
}
}
struct Flight<'a>(&'a AtomicBool);
impl Drop for Flight<'_> {
fn drop(&mut self) {
self.0.store(false, Ordering::Release);
}
}
fn lock_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}