rightkit_db/
checkpoint_job.rs1use crate::{checkpoint, checkpoint_restart, wal_path, CheckpointStatus, Result};
13use rusqlite::Connection;
14use std::path::{Path, PathBuf};
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::{Mutex, MutexGuard};
17use std::time::{Duration, Instant};
18
19const MIB: u64 = 1024 * 1024;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct WalCheckpointPolicy {
24 pub passive_above_bytes: u64,
27 pub restart_above_bytes: u64,
30 pub quiet_for: Duration,
32}
33
34impl Default for WalCheckpointPolicy {
35 fn default() -> Self {
36 Self {
37 passive_above_bytes: 16 * MIB,
38 restart_above_bytes: 256 * MIB,
39 quiet_for: Duration::from_secs(30),
40 }
41 }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum WalCheckpointMode {
47 Passive,
48 Restart,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct WalCheckpointRun {
54 pub mode: WalCheckpointMode,
55 pub wal_bytes: u64,
57 pub status: CheckpointStatus,
59}
60
61#[derive(Debug)]
63pub struct WalCheckpointJob {
64 wal: PathBuf,
65 policy: WalCheckpointPolicy,
66 last_activity: Mutex<Instant>,
67 running: AtomicBool,
68}
69
70impl WalCheckpointJob {
71 pub fn new(db: impl AsRef<Path>, policy: WalCheckpointPolicy) -> Result<Self> {
73 Ok(Self {
74 wal: wal_path(db.as_ref())?,
75 policy,
76 last_activity: Mutex::new(Instant::now()),
77 running: AtomicBool::new(false),
78 })
79 }
80
81 pub fn policy(&self) -> WalCheckpointPolicy {
83 self.policy
84 }
85
86 pub fn note_activity(&self) {
89 self.note_activity_at(Instant::now());
90 }
91
92 pub fn note_activity_at(&self, now: Instant) {
94 let mut last = lock_recover(&self.last_activity);
95 if now > *last {
96 *last = now;
97 }
98 }
99
100 pub fn tick(&self, conn: &Connection) -> Result<Option<WalCheckpointRun>> {
103 self.tick_at(conn, Instant::now())
104 }
105
106 pub fn tick_at(&self, conn: &Connection, now: Instant) -> Result<Option<WalCheckpointRun>> {
108 if self
109 .running
110 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
111 .is_err()
112 {
113 return Ok(None);
114 }
115 let _flight = Flight(&self.running);
116 let wal_bytes = std::fs::metadata(&self.wal).map_or(0, |m| m.len());
117 if wal_bytes == 0 {
118 return Ok(None);
119 }
120 let idle = now.saturating_duration_since(*lock_recover(&self.last_activity));
121 let restart_due =
122 wal_bytes >= self.policy.restart_above_bytes && idle >= self.policy.quiet_for;
123 let mode = if restart_due {
124 WalCheckpointMode::Restart
125 } else if wal_bytes >= self.policy.passive_above_bytes {
126 WalCheckpointMode::Passive
127 } else {
128 return Ok(None);
129 };
130 let status = match mode {
131 WalCheckpointMode::Restart => checkpoint_restart(conn)?,
132 WalCheckpointMode::Passive => checkpoint(conn, "PRAGMA wal_checkpoint(PASSIVE)")?,
133 };
134 Ok(Some(WalCheckpointRun {
135 mode,
136 wal_bytes,
137 status,
138 }))
139 }
140}
141
142struct Flight<'a>(&'a AtomicBool);
144
145impl Drop for Flight<'_> {
146 fn drop(&mut self) {
147 self.0.store(false, Ordering::Release);
148 }
149}
150
151fn lock_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
152 mutex
153 .lock()
154 .unwrap_or_else(|poisoned| poisoned.into_inner())
155}