1mod acquisition;
3mod connection_setup;
4mod construction;
5mod guards;
6mod identity;
7mod pool_impl;
8mod reader_counters;
9
10pub(crate) use connection_setup::open_read_only_snapshot_connection;
11#[cfg(feature = "namespace-trigram-proto")]
12use connection_setup::register_namespace_trigram;
13#[cfg(test)]
14use connection_setup::sqlite_header_uses_wal;
15use connection_setup::{
16 close_connection_quietly, configure_reader_connection, configure_writer_connection,
17 current_journal_mode, effective_reader_count, open_reader_connection, open_writer_connection,
18 pool_exhausted_error, read_only_open_target, reader_connection_is_healthy,
19 reset_reader_connection, restore_shared_reader_state, validate_wal_ceiling_at_open,
20 writer_open_flags, writer_read_only_open_flags,
21};
22#[cfg(any(unix, windows))]
23use identity::database_file_identity_if_exists;
24#[cfg(test)]
25use identity::initialize_database_id;
26#[cfg(any(unix, windows))]
27pub(crate) use identity::opened_sqlite_file_identity;
28use identity::{initialize_database_id_with_admission, mint_db_identity, read_database_id};
29
30use guards::{PooledReader, ReaderLease};
31pub(crate) use guards::{ReaderAdmission, SharedReaderTransactionGuard};
32pub use guards::{ReaderGuard, ReaderRow};
33use reader_counters::ReaderAcquisitionCounters;
34pub use reader_counters::ReaderAcquisitionSnapshot;
35pub(crate) use reader_counters::StandaloneReaderPurpose;
36
37mod code_map;
38mod sql_functions;
39#[cfg(test)]
40pub(crate) use sql_functions::register_rfc3339_key;
41use sql_functions::{register_read_functions, register_writer_clock};
42pub(crate) use sql_functions::{rfc3339_instant_key, strict_rfc3339_key};
43#[path = "pool/identity_registry.rs"]
44mod identity_registry;
45#[cfg(any(test, feature = "test-support"))]
46#[path = "pool/test_volume_lock_dir.rs"]
47mod test_volume_lock_dir;
48#[path = "pool/write_units.rs"]
49mod write_units;
50#[path = "pool/writer_acquisition.rs"]
51mod writer_acquisition;
52#[cfg(test)]
53use identity_registry::pool_identity_suffix;
54use identity_registry::PoolIdentityRegistration;
55#[cfg(any(test, feature = "test-support"))]
56use test_volume_lock_dir::test_volume_lock_dir;
57#[cfg(test)]
58use write_units::STARTUP_SPACE_PROBE;
59pub use write_units::{CheckpointGuard, CheckpointResult, WriterAcquisitionSnapshot, WriterGuard};
60pub(crate) use write_units::{
61 PooledAutocommitWriteUnit, PooledTransactionWriteUnit, StandaloneTransactionWriteUnit,
62 WriteAdmission, WriterAcquisitionCounters,
63};
64
65use crossbeam_queue::ArrayQueue;
66use parking_lot::{Condvar, Mutex};
67use rusqlite::hooks::{AuthContext, Authorization};
68use rusqlite::{Connection, OpenFlags};
69use serde::Serialize;
70use sha2::{Digest, Sha256};
71#[cfg(test)]
72use std::cell::Cell;
73use std::collections::{BTreeMap, HashMap};
74use std::fs;
75use std::ops::{Deref, DerefMut};
76use std::path::{Path, PathBuf};
77use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
78use std::sync::{Arc, OnceLock};
79#[cfg(test)]
80use std::thread;
81use std::time::{Duration, Instant};
82use tokio::sync::Semaphore;
83
84#[cfg(test)]
85use crate::database_owner_identity::DatabaseOwnerIdentityError;
86use crate::disk_guard::{LeaseRefusal, VolumeIdentity, VolumeLease};
87use crate::disk_guard_config::{EffectiveDiskGuardConfig, DEFAULT_DISK_GUARD_DEADLINE_MS};
88use crate::error::SqliteError;
89#[cfg(all(test, any(unix, windows)))]
90use crate::file_identity::database_file_identity;
91#[cfg(any(unix, windows))]
92use crate::file_identity::DatabaseFileIdentity;
93use crate::writer_task::{execute_wrapped_transaction, WriterTaskHandle};
94use khive_storage::error::StorageError;
95use khive_storage::tx_registry::TxOrigin;
96use khive_storage::CapacityUnavailablePhase;
97use khive_storage::StorageCapability;
98
99mod claimed_file_identity;
100
101#[cfg(unix)]
102mod claimed_file_observer;
103
104#[cfg(unix)]
105pub use claimed_file_observer::initialize as initialize_claimed_file_observer;
106
107#[cfg(all(test, unix))]
108#[path = "pool/claimed_file_identity_tests.rs"]
109mod claimed_file_identity_tests;
110
111pub(crate) const DEFAULT_SQLITE_CACHE_KIB: i64 = 8 * 1024;
120pub(crate) const SQLITE_CACHE_KIB_RANGE: std::ops::RangeInclusive<i64> = 512..=1_048_576;
121const SQLITE_CACHE_KIB_ENV: &str = "KHIVE_SQLITE_CACHE_KIB";
122
123pub(crate) const DEFAULT_SQLITE_MMAP_BYTES: i64 = 1 << 30;
130pub(crate) const SQLITE_MMAP_BYTES_RANGE: std::ops::RangeInclusive<i64> = 0..=(1 << 40);
131const SQLITE_MMAP_BYTES_ENV: &str = "KHIVE_SQLITE_MMAP_BYTES";
132const DEFAULT_READER_CAP: usize = 8;
133
134pub(crate) fn sqlite_cache_size_pragma() -> i64 {
137 static KIB: OnceLock<i64> = OnceLock::new();
138 -*KIB.get_or_init(|| {
139 sqlite_size_setting(
140 SQLITE_CACHE_KIB_ENV,
141 sqlite_size_env(SQLITE_CACHE_KIB_ENV).as_deref(),
142 DEFAULT_SQLITE_CACHE_KIB,
143 &SQLITE_CACHE_KIB_RANGE,
144 )
145 })
146}
147
148pub(crate) fn sqlite_mmap_size_pragma() -> i64 {
151 static BYTES: OnceLock<i64> = OnceLock::new();
152 *BYTES.get_or_init(|| {
153 sqlite_size_setting(
154 SQLITE_MMAP_BYTES_ENV,
155 sqlite_size_env(SQLITE_MMAP_BYTES_ENV).as_deref(),
156 DEFAULT_SQLITE_MMAP_BYTES,
157 &SQLITE_MMAP_BYTES_RANGE,
158 )
159 })
160}
161
162fn sqlite_size_env(name: &str) -> Option<String> {
166 std::env::var_os(name).map(|raw| raw.to_string_lossy().into_owned())
167}
168
169pub(crate) fn sqlite_size_setting(
173 name: &str,
174 raw: Option<&str>,
175 default: i64,
176 range: &std::ops::RangeInclusive<i64>,
177) -> i64 {
178 let Some(raw) = raw else {
179 return default;
180 };
181 match raw.parse::<i64>() {
182 Ok(value) if range.contains(&value) => value,
183 _ => {
184 tracing::warn!(
185 setting = name,
186 value = raw,
187 default,
188 min = *range.start(),
189 max = *range.end(),
190 "ignoring a SQLite size setting outside its range; using the default"
191 );
192 default
193 }
194 }
195}
196
197const DEFAULT_JOURNAL_SIZE_LIMIT_BYTES: i64 = 67_108_864; const DEFAULT_WRITE_QUEUE_CAPACITY: usize = 256;
199const DATABASE_ID_TABLE: &str = "_khive_database_identity";
200static NEXT_MAIN_POOL_GENERATION: AtomicU64 = AtomicU64::new(1);
201
202#[cfg(test)]
203#[derive(Clone, Copy, PartialEq, Eq)]
204enum IdentityOpenStage {
205 AfterMainOpenBeforeFirstStat,
206 AfterInitialIdentityWrite,
207 BeforeStandaloneOpen,
208 AfterStandaloneOpen,
209}
210
211#[cfg(test)]
212type IdentityOpenHook = Box<dyn Fn(&Path, IdentityOpenStage, Option<&Connection>)>;
213
214#[cfg(test)]
215thread_local! {
216 static IDENTITY_OPEN_HOOK: std::cell::RefCell<Option<IdentityOpenHook>> =
217 const { std::cell::RefCell::new(None) };
218}
219
220#[cfg(test)]
221fn run_identity_open_hook(path: &Path, stage: IdentityOpenStage, conn: Option<&Connection>) {
222 IDENTITY_OPEN_HOOK.with(|hook| {
223 if let Some(hook) = hook.borrow().as_ref() {
224 hook(path, stage, conn);
225 }
226 });
227}
228
229#[path = "pool/admission.rs"]
230mod admission;
231use admission::CheckpointOwnershipGate;
232pub use admission::RuntimeWriteOperation;
233pub(crate) use admission::FALLBACK_WAL_AUTOCHECKPOINT_PAGES;
234#[cfg(test)]
235use admission::{CheckpointConnectionConfigPause, CheckpointOwnership};
236
237fn deny_retired_writer(_context: AuthContext<'_>) -> Authorization {
238 Authorization::Deny
239}
240
241mod config;
242
243pub(crate) use config::TEST_HARNESS_ENV;
244use config::{refuse_home_data_store_in_tests, validate_write_admission_deadline};
245pub use config::{PoolConfig, WalCeilingPolicy, WalCeilingSource};
246
247#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)]
249pub struct SearchMechanismSnapshot {
250 pub dispatches_by_backend_and_kind: BTreeMap<String, BTreeMap<String, u64>>,
254 pub note_candidate_hydration_rows: u64,
258}
259
260pub struct ConnectionPool {
274 writer: Arc<Mutex<Connection>>,
275 retirement_connection: Mutex<Option<Connection>>,
277 #[cfg(any(test, feature = "test-support"))]
278 statement_observer: Arc<crate::statement_observer::StatementObserverHub>,
279 main_pool_generation: OnceLock<u64>,
280 checkpoint_ownership: CheckpointOwnershipGate,
289 pooled_writer_retired: AtomicBool,
294 writer_acquisition_counters: Arc<WriterAcquisitionCounters>,
299 write_admission: Arc<WriteAdmission>,
302 disk_guard_config: Option<EffectiveDiskGuardConfig>,
305 reader_acquisition_counters: ReaderAcquisitionCounters,
310 reader_checkouts: Mutex<BTreeMap<Instant, usize>>,
311 search_dispatches: Mutex<BTreeMap<String, BTreeMap<String, u64>>>,
314 note_candidate_hydration_rows: AtomicU64,
315 readers: ArrayQueue<Box<PooledReader>>,
316 max_readers: usize,
317 config: PoolConfig,
318 read_only_open_target: Option<PathBuf>,
326 sql_bridge_reader_slots: Arc<Semaphore>,
327 sql_bridge_writer_slots: Arc<Semaphore>,
328 writer_task: OnceLock<Option<WriterTaskHandle>>,
333 writer_task_join: Mutex<Option<tokio::task::JoinHandle<()>>>,
341 writer_task_join_stored: AtomicBool,
346 origin: TxOrigin,
352 identity_path: Option<PathBuf>,
358 #[cfg(any(unix, windows))]
361 opened_file_identity: Option<DatabaseFileIdentity>,
362 opened_database_id: Option<uuid::Uuid>,
365 identity_registration: Option<PoolIdentityRegistration>,
368 #[cfg(test)]
374 writer_task_spawn_count: std::sync::atomic::AtomicUsize,
375}
376
377impl Drop for ConnectionPool {
378 fn drop(&mut self) {
386 while let Some(conn) = self.readers.pop() {
387 drop(conn);
388 }
389 }
390}
391
392#[cfg(test)]
393#[path = "runtime_write_routing_tests.rs"]
394mod runtime_write_routing_tests;
395
396#[cfg(test)]
397#[path = "database_owner_identity_pool_tests.rs"]
398mod database_owner_identity_pool_tests;
399
400#[cfg(test)]
401#[path = "pool_tests.rs"]
402mod tests;
403
404#[cfg(test)]
405#[path = "pool/recycling_tests.rs"]
406mod recycling_tests;
407
408#[cfg(all(test, any(unix, windows)))]
409#[path = "pool_identity_admission_tests.rs"]
410mod identity_admission_tests;