1use super::freelist::FreeList;
9use super::page::{Page, PageError, PageType, PAGE_SIZE};
10use super::page_cache::PageCache;
11use super::pager_io::{self, FIRST_ALLOCATABLE_PAGE};
12use reddb_file::VfsFile;
13pub use reddb_file::{DatabaseHeader, PhysicalFileHeader};
14use std::path::{Path, PathBuf};
15use std::sync::{Mutex, RwLock};
16
17#[derive(Debug)]
19pub enum PagerError {
20 Io(std::io::Error),
22 Page(PageError),
24 InvalidDatabase(String),
26 ReadOnly,
28 PageNotFound(u32),
30 Locked,
32 LockPoisoned,
34 EncryptionRequired,
36 PlainDatabaseRefusesKey,
38 InvalidKey,
40 SuperblockZoneUnrecoverable(PathBuf),
42 ManifestZoneCorrupt(PathBuf),
44 DwbZoneCorrupt(PathBuf),
46 LegacySidecarStore { sidecar: PathBuf },
48 LegacyPagedStore { path: PathBuf },
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct ExtentId {
55 pub start_page: u32,
56 pub n_pages: u32,
57}
58
59impl std::fmt::Display for PagerError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 Self::Io(e) => write!(f, "I/O error: {}", e),
63 Self::Page(e) => write!(f, "Page error: {}", e),
64 Self::InvalidDatabase(msg) => write!(f, "Invalid database: {}", msg),
65 Self::ReadOnly => write!(f, "Database is read-only"),
66 Self::PageNotFound(id) => write!(f, "Page {} not found", id),
67 Self::Locked => write!(f, "Database is locked"),
68 Self::LockPoisoned => write!(f, "Internal lock poisoned (concurrent thread panicked)"),
69 Self::EncryptionRequired => write!(
70 f,
71 "Database is encrypted but no key was supplied (set PagerConfig::encryption)"
72 ),
73 Self::PlainDatabaseRefusesKey => write!(
74 f,
75 "Plain (unencrypted) database opened with an encryption key — refusing"
76 ),
77 Self::InvalidKey => write!(f, "Encryption key validation failed for this database"),
78 Self::SuperblockZoneUnrecoverable(path) => write!(
79 f,
80 "superblock zone of {} is unrecoverable: both ping-pong copies failed \
81 validation, so no generation can be trusted to root the store. The store \
82 will not be opened. Recover what survives with red salvage \
83 (ADR 0074 §4); it reads the damaged file without writing to it and \
84 reports what it could not verify.",
85 path.display()
86 ),
87 Self::ManifestZoneCorrupt(path) => write!(
88 f,
89 "internal manifest zone of {} failed its checksum: the zone that names \
90 collections, indexes and the checkpoint boundary cannot be trusted, so no \
91 rows are returned rather than garbage ones. Run the `SCRUB` statement to classify the fault \
92 and `red salvage` to extract what survives (ADR 0074 §2/§4).",
93 path.display()
94 ),
95 Self::DwbZoneCorrupt(path) => write!(
96 f,
97 "double-write zone of {} failed validation: page recovery cannot prove \
98 whether the protected in-place write completed. The store will not be opened \
99 silently; run the `SCRUB` statement to classify the fault and `red salvage` to extract trusted \
100 rows (ADR 0074 §2/§4).",
101 path.display()
102 ),
103 Self::LegacySidecarStore { sidecar } => write!(
104 f,
105 "refusing to open a legacy sidecar-backed store: found {}. The superblock \
106 pair, internal manifest, and double-write recovery state now live inside the \
107 .rdb file (ADR 0038 §2/§4), \
108 and the engine never reads the retired sidecars silently. Convert the store \
109 first with the offline migration tool \
110 (`reddb_server::pager_zone_migration::migrate_to_zoned`); it is reversible.",
111 sidecar.display()
112 ),
113 Self::LegacyPagedStore { path } => write!(
114 f,
115 "refusing to open a pre-phase-3 paged store: {} does not carry the layout marker \
116 for the in-file double-write zone. Convert the store first with the offline \
117 migration tool (`reddb_server::pager_zone_migration::migrate_to_zoned`); it is \
118 reversible.",
119 path.display()
120 ),
121 }
122 }
123}
124
125impl std::error::Error for PagerError {
126 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
127 match self {
128 Self::Io(e) => Some(e),
129 Self::Page(e) => Some(e),
130 _ => None,
131 }
132 }
133}
134
135impl From<std::io::Error> for PagerError {
136 fn from(e: std::io::Error) -> Self {
137 Self::Io(e)
138 }
139}
140
141impl From<PageError> for PagerError {
142 fn from(e: PageError) -> Self {
143 Self::Page(e)
144 }
145}
146
147impl From<pager_io::PagerIoError> for PagerError {
148 fn from(error: pager_io::PagerIoError) -> Self {
149 match error {
150 pager_io::PagerIoError::Io(error) => Self::Io(error),
151 pager_io::PagerIoError::Page(error) => Self::Page(error),
152 pager_io::PagerIoError::InvalidDatabase(message) => Self::InvalidDatabase(message),
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct PagerConfig {
160 pub disk_residency: bool,
162 pub cache_size: usize,
163 pub read_only: bool,
164 pub verify_checksums: bool,
165 pub double_write: bool,
166}
167
168impl Default for PagerConfig {
169 fn default() -> Self {
170 Self {
171 disk_residency: false,
172 cache_size: 10_000,
173 read_only: false,
174 verify_checksums: true,
175 double_write: true,
176 }
177 }
178}
179
180pub trait WalBarrier {
183 fn flush_until(&self, lsn: u64) -> std::io::Result<()>;
184
185 fn needs_page_image(&self, _page_id: u32) -> std::io::Result<bool> {
188 Ok(false)
189 }
190
191 fn capture_page_image(&self, _page: &Page) -> std::io::Result<()> {
194 Ok(())
195 }
196
197 fn reserve_pages(&self, _start: u32, _count: u32) -> std::io::Result<()> {
199 Ok(())
200 }
201
202 fn detach_free_pages(&self, _pages: &[u32]) -> std::io::Result<()> {
204 Ok(())
205 }
206
207 fn has_page_ownership_journal(&self) -> std::io::Result<bool> {
210 Ok(false)
211 }
212
213 fn retire_pages(&self, _pages: &[u32]) -> std::io::Result<()> {
215 Ok(())
216 }
217}
218
219pub struct NoWal;
221impl WalBarrier for NoWal {
222 fn flush_until(&self, _lsn: u64) -> std::io::Result<()> {
223 Err(std::io::Error::other(
224 "nonzero page LSN requires a WAL durability barrier",
225 ))
226 }
227}
228pub struct Pager<F: VfsFile, W: WalBarrier> {
229 path: PathBuf,
231 file: Mutex<F>,
233 dwb_enabled: bool,
235 writeback_failed: std::sync::atomic::AtomicBool,
238 cache: PageCache,
240 freelist: RwLock<FreeList>,
242 deferred_reclamation: Mutex<DeferredPageReclamation>,
245 ownership_mutations: parking_lot::ReentrantMutex<()>,
248 allocation_reused: std::sync::atomic::AtomicBool,
251 allocation_fence_pending: std::sync::atomic::AtomicBool,
253 header: RwLock<DatabaseHeader>,
255 superblock_publication: Mutex<()>,
257 config: PagerConfig,
259 header_dirty: Mutex<bool>,
261 wal: W,
262}
263
264#[derive(Default)]
265struct DeferredPageReclamation {
266 enabled: bool,
267 pending: std::collections::BTreeSet<u32>,
268 published: std::collections::BTreeSet<u32>,
269}
270
271const SUPERBLOCK_SLOT_SIZE: usize = reddb_file::PAGED_SUPERBLOCK_SLOT_SIZE;
273const SUPERBLOCK_ZONE_SIZE: usize = reddb_file::PAGED_SUPERBLOCK_ZONE_SIZE;
274const PHASE3_DWB_ZONE_FILE_VERSION: u32 = 0x0001_0001;
277
278pub type NewestSuperblock = (
280 reddb_file::PagedSuperblockSelection,
281 [u8; SUPERBLOCK_SLOT_SIZE],
282);
283
284fn superblock_slot_image(page: &[u8; PAGE_SIZE]) -> [u8; SUPERBLOCK_SLOT_SIZE] {
287 let mut image = [0u8; SUPERBLOCK_SLOT_SIZE];
288 image.copy_from_slice(&page[..SUPERBLOCK_SLOT_SIZE]);
289 image
290}
291
292impl<F: VfsFile, W: WalBarrier> Pager<F, W> {
293 pub fn open_owned(
297 file: F,
298 path: PathBuf,
299 config: PagerConfig,
300 wal: W,
301 create: bool,
302 ) -> Result<Self, PagerError> {
303 if create && file.file_len()? != 0 {
304 return Err(PagerError::InvalidDatabase(
305 "new pager file is not empty".into(),
306 ));
307 }
308 let pager = Self {
309 path,
310 file: Mutex::new(file),
311 dwb_enabled: config.double_write && !config.read_only,
312 writeback_failed: std::sync::atomic::AtomicBool::new(false),
313 cache: PageCache::new(config.cache_size),
314 freelist: RwLock::new(FreeList::new()),
315 deferred_reclamation: Mutex::new(DeferredPageReclamation::default()),
316 ownership_mutations: parking_lot::ReentrantMutex::new(()),
317 allocation_reused: std::sync::atomic::AtomicBool::new(false),
318 allocation_fence_pending: std::sync::atomic::AtomicBool::new(false),
319 header: RwLock::new(DatabaseHeader::default()),
320 superblock_publication: Mutex::new(()),
321 config,
322 header_dirty: Mutex::new(false),
323 wal,
324 };
325 if create {
326 pager.initialize()?;
327 } else {
328 pager.ensure_phase3_dwb_zone_layout()?;
329 pager.recover_from_dwb()?;
330 pager.load_header()?;
331 }
332 Ok(pager)
333 }
334
335 pub fn wal_barrier(&self) -> &W {
336 &self.wal
337 }
338
339 pub fn lock_ownership_mutations(&self) -> parking_lot::ReentrantMutexGuard<'_, ()> {
342 self.ownership_mutations.lock()
343 }
344
345 pub fn fence_interrupted_ownership(&self) {
347 self.writeback_failed
348 .store(true, std::sync::atomic::Ordering::Release);
349 }
350
351 #[doc(hidden)]
354 pub fn with_file<T>(&self, operation: impl FnOnce(&mut F) -> T) -> Result<T, PagerError> {
355 Ok(operation(&mut *self.file_lock()?))
356 }
357
358 fn initialize(&self) -> Result<(), PagerError> {
360 if self.config.read_only {
361 return Err(PagerError::ReadOnly);
362 }
363
364 let initial_page_count = FIRST_ALLOCATABLE_PAGE;
368 let header_page = Page::new_header_page(initial_page_count);
369 self.header_write()?.page_count = initial_page_count;
370
371 self.write_page_raw(0, &header_page)?;
376 let mut metadata_page = Page::new(PageType::Header, 1);
377 metadata_page.update_checksum();
378 self.write_page_raw(1, &metadata_page)?;
379 let mut vault_page = Page::new(PageType::Vault, 2);
380 vault_page.update_checksum();
381 self.write_page_raw(2, &vault_page)?;
382 self.clear_dwb_zone()?;
383
384 let mut slot = superblock_slot_image(header_page.as_bytes());
388 self.write_superblock_slot(&mut slot, 0, 1)?;
389 self.write_superblock_slot(&mut slot, 1, 2)?;
390
391 self.sync()?;
393
394 Ok(())
395 }
396
397 fn header_write(&self) -> Result<std::sync::RwLockWriteGuard<'_, DatabaseHeader>, PagerError> {
399 self.header.write().map_err(|_| PagerError::LockPoisoned)
400 }
401
402 fn header_read(&self) -> Result<std::sync::RwLockReadGuard<'_, DatabaseHeader>, PagerError> {
404 self.header.read().map_err(|_| PagerError::LockPoisoned)
405 }
406
407 fn freelist_write(&self) -> Result<std::sync::RwLockWriteGuard<'_, FreeList>, PagerError> {
409 self.freelist.write().map_err(|_| PagerError::LockPoisoned)
410 }
411
412 fn file_lock(&self) -> Result<std::sync::MutexGuard<'_, F>, PagerError> {
414 self.file.lock().map_err(|_| PagerError::LockPoisoned)
415 }
416
417 fn header_dirty_lock(&self) -> Result<std::sync::MutexGuard<'_, bool>, PagerError> {
419 self.header_dirty
420 .lock()
421 .map_err(|_| PagerError::LockPoisoned)
422 }
423
424 fn read_superblock_zone(&self) -> Result<[u8; SUPERBLOCK_ZONE_SIZE], PagerError> {
428 let mut file = self.file_lock()?;
429 Ok(pager_io::read_superblock_zone(&mut *file)?)
430 }
431
432 #[doc(hidden)]
438 pub fn newest_superblock(&self) -> Result<NewestSuperblock, PagerError> {
439 let zone = self.read_superblock_zone()?;
440 let selection = reddb_file::select_paged_superblock(&zone)
441 .ok_or_else(|| PagerError::SuperblockZoneUnrecoverable(self.path.clone()))?;
442
443 let start = selection.copy_index * SUPERBLOCK_SLOT_SIZE;
444 let mut image = [0u8; SUPERBLOCK_SLOT_SIZE];
445 image.copy_from_slice(&zone[start..start + SUPERBLOCK_SLOT_SIZE]);
446 Ok((selection, image))
447 }
448
449 fn ensure_phase3_dwb_zone_layout(&self) -> Result<(), PagerError> {
450 let (_, image) = self.newest_superblock()?;
451 let header = reddb_file::decode_database_header(&image)
452 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
453 if header.version >= reddb_file::file_format::PAGE_DISK_RESIDENCY_FILE_VERSION
454 && !self.config.disk_residency
455 && !self.config.read_only
456 {
457 return Err(PagerError::InvalidDatabase(
458 "prepared disk-resident database requires a disk-resident writer".into(),
459 ));
460 }
461 if header.version < PHASE3_DWB_ZONE_FILE_VERSION {
462 return Err(PagerError::LegacyPagedStore {
463 path: self.path.clone(),
464 });
465 }
466 Ok(())
467 }
468
469 fn write_superblock_slot(
472 &self,
473 slot: &mut [u8; SUPERBLOCK_SLOT_SIZE],
474 copy_index: usize,
475 generation: u64,
476 ) -> Result<(), PagerError> {
477 self.ensure_writeback_available()?;
478 if self.config.read_only {
479 return Err(PagerError::ReadOnly);
480 }
481 let mut file = self.file_lock()?;
482 self.ensure_writeback_available()?;
483 Ok(pager_io::write_superblock_slot(
484 &mut *file, slot, copy_index, generation,
485 )?)
486 }
487
488 #[doc(hidden)]
494 pub fn commit_superblock(
495 &self,
496 mutate: impl FnOnce(&mut [u8; SUPERBLOCK_SLOT_SIZE]) -> Result<(), PagerError>,
497 ) -> Result<(), PagerError> {
498 let _ownership = self.lock_ownership_mutations();
499 let _publication = self
500 .superblock_publication
501 .lock()
502 .map_err(|_| PagerError::LockPoisoned)?;
503 self.commit_superblock_locked(mutate)
504 }
505
506 fn commit_superblock_locked(
507 &self,
508 mutate: impl FnOnce(&mut [u8; SUPERBLOCK_SLOT_SIZE]) -> Result<(), PagerError>,
509 ) -> Result<(), PagerError> {
510 if self.config.read_only {
511 return Err(PagerError::ReadOnly);
512 }
513 let (newest, mut image) = self.newest_superblock()?;
514 mutate(&mut image)?;
515 let next_copy = reddb_file::paged_superblock_next_copy(Some(newest));
516 self.write_superblock_slot(&mut image, next_copy, newest.generation.saturating_add(1))
517 }
518
519 pub fn fence_file_format_version(&self, version: u32) -> Result<(), PagerError> {
525 let _ownership = self.lock_ownership_mutations();
526 self.ensure_writeback_available()?;
527 if self.config.read_only {
528 return Err(PagerError::ReadOnly);
529 }
530 if version >= reddb_file::file_format::PAGE_DISK_RESIDENCY_FILE_VERSION
531 && !self.config.disk_residency
532 {
533 return Err(PagerError::InvalidDatabase(
534 "disk-residency format requires a disk-resident writer".into(),
535 ));
536 }
537 if !(PHASE3_DWB_ZONE_FILE_VERSION..=reddb_file::PAGE_FILE_VERSION).contains(&version) {
538 return Err(PagerError::InvalidDatabase(
539 "unsupported file format fence".into(),
540 ));
541 }
542 let _publication = self
543 .superblock_publication
544 .lock()
545 .map_err(|_| PagerError::LockPoisoned)?;
546 if version < self.header_read()?.version {
547 return Err(PagerError::InvalidDatabase(
548 "file format downgrade is not supported".into(),
549 ));
550 }
551 let result = (|| {
552 for _ in 0..reddb_file::PAGED_SUPERBLOCK_SLOT_COUNT {
553 self.commit_superblock_locked(|image| {
554 reddb_file::set_database_header_version(image, version)
555 .map_err(|error| PagerError::InvalidDatabase(error.to_string()))
556 })?;
557 }
558 self.header_write()?.version = version;
559 Ok(())
560 })();
561 if result.is_err() {
562 self.writeback_failed
563 .store(true, std::sync::atomic::Ordering::Release);
564 }
565 result
566 }
567
568 fn load_header(&self) -> Result<(), PagerError> {
570 let (_, image) = self.newest_superblock()?;
571
572 let mut decoded_header = reddb_file::decode_database_header(&image)
573 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
574 let physical_pages = self.file_lock()?.file_len()?.div_ceil(PAGE_SIZE as u64);
580 let physical_pages = u32::try_from(physical_pages).map_err(|_| {
581 PagerError::InvalidDatabase("data file exceeds the page address space".into())
582 })?;
583 if decoded_header.page_count < physical_pages {
584 decoded_header.page_count = physical_pages;
585 if !self.config.read_only {
586 *self.header_dirty_lock()? = true;
587 }
588 }
589 let freelist_head = decoded_header.freelist_head;
590
591 {
592 let mut header = self.header_write()?;
593 *header = decoded_header;
594 }
595
596 {
598 let mut freelist = self.freelist_write()?;
599 *freelist = FreeList::from_header(freelist_head, 0);
600 }
601
602 Ok(())
603 }
604
605 fn write_header(&self) -> Result<(), PagerError> {
607 if self.config.read_only {
608 return Err(PagerError::ReadOnly);
609 }
610
611 let header = self.header_read()?.clone();
612 self.commit_superblock(|image| {
613 reddb_file::encode_database_header(image, &header)
614 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))
615 })?;
616 *self.header_dirty_lock()? = false;
617
618 Ok(())
619 }
620
621 fn read_page_raw(&self, page_id: u32) -> Result<Page, PagerError> {
623 let mut file = self.file_lock()?;
624 Ok(pager_io::read_page(
625 &mut *file,
626 page_id,
627 self.config.verify_checksums,
628 )?)
629 }
630
631 fn ensure_writeback_available(&self) -> Result<(), PagerError> {
632 if self
633 .writeback_failed
634 .load(std::sync::atomic::Ordering::Acquire)
635 {
636 return Err(PagerError::Io(std::io::Error::other(
637 "page writeback failed; reopen the database to recover before writing",
638 )));
639 }
640 Ok(())
641 }
642
643 fn record_ownership(
644 &self,
645 operation: impl FnOnce(&W) -> std::io::Result<()>,
646 ) -> Result<(), PagerError> {
647 if let Err(error) = operation(&self.wal) {
648 self.writeback_failed
649 .store(true, std::sync::atomic::Ordering::Release);
650 return Err(PagerError::Io(error));
651 }
652 Ok(())
653 }
654
655 fn write_evicted_dirty_page(
658 &self,
659 page: Page,
660 pending_page: Option<u32>,
661 ) -> Result<(), PagerError> {
662 let mut ids = self.cache.dirty_page_ids(pager_io::DWB_MAX_PAGES_PER_FRAME);
663 ids.retain(|id| Some(*id) != pending_page);
667 ids.truncate(pager_io::DWB_MAX_PAGES_PER_FRAME.saturating_sub(1));
670 ids.sort_unstable();
671 let mut pages = self.cache.flush_dirty_pages(&ids);
672 pages.push((page.page_id(), page));
673 pages.sort_unstable_by_key(|(id, _)| *id);
674 self.write_dirty_page_batch(&pages)
675 }
676
677 fn write_page_raw(&self, page_id: u32, page: &Page) -> Result<(), PagerError> {
679 self.ensure_writeback_available()?;
680 if self.config.read_only {
681 return Err(PagerError::ReadOnly);
682 }
683 let mut file = self.file_lock()?;
684 self.ensure_writeback_available()?;
685 Ok(pager_io::write_page(&mut *file, page_id, page)?)
686 }
687
688 pub fn read_page(&self, page_id: u32) -> Result<Page, PagerError> {
690 if let Some(page) = self.cache.get(page_id) {
692 return Ok(page);
693 }
694
695 let _ownership = self.lock_ownership_mutations();
699 if let Some(page) = self.cache.get(page_id) {
700 return Ok(page);
701 }
702
703 let page = self.read_page_raw(page_id)?;
705
706 if let Some(dirty_page) = self.cache.insert_if_absent(page_id, page.clone()) {
708 self.write_evicted_dirty_page(dirty_page, None)?;
710 }
711
712 Ok(page)
713 }
714
715 pub fn read_page_no_checksum(&self, page_id: u32) -> Result<Page, PagerError> {
720 if let Some(page) = self.cache.get(page_id) {
722 return Ok(page);
723 }
724
725 let _ownership = self.lock_ownership_mutations();
726 if let Some(page) = self.cache.get(page_id) {
727 return Ok(page);
728 }
729
730 let mut file = self.file_lock()?;
732 let page = pager_io::read_page(&mut *file, page_id, false)?;
733 drop(file);
734
735 if let Some(dirty_page) = self.cache.insert_if_absent(page_id, page.clone()) {
737 self.write_evicted_dirty_page(dirty_page, None)?;
739 }
740
741 Ok(page)
742 }
743
744 pub fn write_page(&self, page_id: u32, mut page: Page) -> Result<(), PagerError> {
746 let _ownership = self.lock_ownership_mutations();
747 self.ensure_writeback_available()?;
748 if self.config.read_only {
749 return Err(PagerError::ReadOnly);
750 }
751
752 self.capture_page_before_write(page_id)?;
753
754 page.update_checksum();
756
757 if let Some(dirty_page) = self.cache.insert_dirty(page_id, page) {
762 self.write_evicted_dirty_page(dirty_page, Some(page_id))?;
763 }
764
765 Ok(())
766 }
767
768 pub fn write_page_no_checksum(&self, page_id: u32, page: Page) -> Result<(), PagerError> {
773 let _ownership = self.lock_ownership_mutations();
774 self.ensure_writeback_available()?;
775 if self.config.read_only {
776 return Err(PagerError::ReadOnly);
777 }
778
779 self.capture_page_before_write(page_id)?;
780
781 if let Some(dirty_page) = self.cache.insert_dirty(page_id, page) {
784 self.write_evicted_dirty_page(dirty_page, Some(page_id))?;
785 }
786
787 Ok(())
788 }
789
790 fn capture_page_before_write(&self, page_id: u32) -> Result<(), PagerError> {
791 let mut required = false;
792 self.record_ownership(|wal| {
793 required = wal.needs_page_image(page_id)?;
794 Ok(())
795 })?;
796 if required {
797 let previous = self.read_page(page_id)?;
798 self.record_ownership(|wal| wal.capture_page_image(&previous))?;
799 }
800 Ok(())
801 }
802
803 pub fn allocate_page(&self, page_type: PageType) -> Result<Page, PagerError> {
805 let _ownership = self.lock_ownership_mutations();
806 self.ensure_writeback_available()?;
807 if self.config.read_only {
808 return Err(PagerError::ReadOnly);
809 }
810
811 let page_id = {
813 let mut freelist = self.freelist_write()?;
814 self.complete_allocation_fence()?;
817 if let Some(id) = freelist.next_in_memory_page() {
818 self.record_ownership(|wal| wal.reserve_pages(id, 1))?;
819 let id = freelist
820 .allocate()
821 .expect("free page checked while allocator is locked");
822 self.allocation_reused
823 .store(true, std::sync::atomic::Ordering::Release);
824 id
825 } else if freelist.trunk_head() != 0 {
826 let trunk_id = freelist.trunk_head();
827
828 let trunk = self.read_page(trunk_id).map_err(|e| match e {
829 PagerError::PageNotFound(_) => {
830 PagerError::InvalidDatabase("Freelist trunk missing".to_string())
831 }
832 other => other,
833 })?;
834 if trunk.page_id() != trunk_id {
835 return Err(PagerError::InvalidDatabase(
836 "misdirected freelist trunk".into(),
837 ));
838 }
839
840 let mut detached = FreeList::new();
843 detached
844 .load_from_trunk(&trunk)
845 .map_err(|e| PagerError::InvalidDatabase(format!("Freelist: {e}")))?;
846 let mut pages = Vec::new();
847 let mut seen = std::collections::HashSet::new();
848 let page_count = self.page_count()?;
849 while let Some(id) = detached.allocate() {
850 if id < FIRST_ALLOCATABLE_PAGE || id >= page_count || !seen.insert(id) {
851 return Err(PagerError::InvalidDatabase(
852 "invalid freelist trunk page".into(),
853 ));
854 }
855 pages.push(id);
856 }
857 let mut journaled = false;
858 self.record_ownership(|wal| {
859 journaled = wal.has_page_ownership_journal()?;
860 Ok(())
861 })?;
862 if !journaled {
863 self.allocate_unjournaled_trunk(&mut freelist, trunk, &pages)?
864 } else {
865 self.record_ownership(|wal| wal.detach_free_pages(&pages))?;
866
867 freelist
868 .load_from_trunk(&trunk)
869 .map_err(|e| PagerError::InvalidDatabase(format!("Freelist: {}", e)))?;
870 let mut header = self.header_write()?;
871 header.freelist_head = freelist.trunk_head();
872 *self.header_dirty_lock()? = true;
873 drop(header);
874 self.allocation_fence_pending
879 .store(true, std::sync::atomic::Ordering::Release);
880 self.complete_allocation_fence()?;
881 let next = freelist.next_in_memory_page().ok_or_else(|| {
882 PagerError::InvalidDatabase("Freelist empty after trunk load".to_string())
883 })?;
884 self.record_ownership(|wal| wal.reserve_pages(next, 1))?;
885 let id = freelist.allocate().ok_or_else(|| {
886 PagerError::InvalidDatabase("Freelist empty after trunk load".to_string())
887 })?;
888 self.allocation_reused
889 .store(true, std::sync::atomic::Ordering::Release);
890 id
891 }
892 } else {
893 let mut header = self.header_write()?;
895 let id = header.page_count;
896 let next = id
897 .checked_add(1)
898 .ok_or_else(|| PagerError::InvalidDatabase("page count overflow".into()))?;
899 self.record_ownership(|wal| wal.reserve_pages(id, 1))?;
900 header.page_count = next;
901 *self.header_dirty_lock()? = true;
902 id
903 }
904 };
905
906 let page = Page::new(page_type, page_id);
907
908 if let Some(dirty_page) = self.cache.insert_dirty(page_id, page.clone()) {
920 self.write_evicted_dirty_page(dirty_page, Some(page_id))?;
921 }
922
923 Ok(page)
924 }
925
926 fn allocate_unjournaled_trunk(
930 &self,
931 freelist: &mut FreeList,
932 mut trunk: Page,
933 pages: &[u32],
934 ) -> Result<u32, PagerError> {
935 let id = *pages
936 .first()
937 .expect("validated trunk includes its own page");
938 self.record_ownership(|wal| wal.reserve_pages(id, 1))?;
939 if pages.len() > 1 {
940 let remaining =
941 u32::try_from(pages.len() - 2).expect("a validated trunk fits in one page");
942 trunk.content_mut()[4..8].copy_from_slice(&remaining.to_le_bytes());
943 trunk.update_checksum();
944 self.cache.remove(trunk.page_id());
945 self.write_pages_through_dwb(&[(trunk.page_id(), trunk)])?;
946 freelist.note_durable_trunk_allocation(remaining);
947 } else {
948 freelist
951 .load_from_trunk(&trunk)
952 .map_err(|error| PagerError::InvalidDatabase(error.to_string()))?;
953 self.header_write()?.freelist_head = freelist.trunk_head();
954 *self.header_dirty_lock()? = true;
955 self.allocation_fence_pending
956 .store(true, std::sync::atomic::Ordering::Release);
957 self.complete_allocation_fence()?;
958 assert_eq!(freelist.allocate(), Some(id));
959 }
960 self.allocation_reused
961 .store(true, std::sync::atomic::Ordering::Release);
962 Ok(id)
963 }
964
965 pub fn reserve_contig_extent(&self, n_pages: u32) -> Result<ExtentId, PagerError> {
967 let _ownership = self.lock_ownership_mutations();
968 self.ensure_writeback_available()?;
969 if self.config.read_only {
970 return Err(PagerError::ReadOnly);
971 }
972 if n_pages == 0 {
973 return Err(PagerError::InvalidDatabase(
974 "contiguous extent must reserve at least one page".to_string(),
975 ));
976 }
977
978 let start_page = {
979 let mut header = self.header_write()?;
980 let start = header.page_count;
981 let end = header.page_count.checked_add(n_pages).ok_or_else(|| {
982 PagerError::InvalidDatabase("contiguous extent page count overflow".to_string())
983 })?;
984 self.record_ownership(|wal| wal.reserve_pages(start, n_pages))?;
985 header.page_count = end;
986 *self.header_dirty_lock()? = true;
987 start
988 };
989
990 for page_id in start_page..start_page + n_pages {
991 let mut page = Page::new(PageType::Vector, page_id);
992 page.update_checksum();
993 if let Some(dirty_page) = self.cache.insert_dirty(page_id, page) {
994 self.write_evicted_dirty_page(dirty_page, Some(page_id))?;
995 }
996 }
997
998 Ok(ExtentId {
999 start_page,
1000 n_pages,
1001 })
1002 }
1003
1004 pub fn free_page(&self, page_id: u32) -> Result<(), PagerError> {
1006 let _ownership = self.lock_ownership_mutations();
1007 self.ensure_writeback_available()?;
1008 if self.config.read_only {
1009 return Err(PagerError::ReadOnly);
1010 }
1011
1012 let mut deferred = self
1013 .deferred_reclamation
1014 .lock()
1015 .map_err(|_| PagerError::LockPoisoned)?;
1016 if deferred.enabled {
1017 if page_id < FIRST_ALLOCATABLE_PAGE || page_id >= self.page_count()? {
1018 return Err(PagerError::InvalidDatabase(
1019 "invalid deferred free page".into(),
1020 ));
1021 }
1022 self.record_ownership(|wal| wal.retire_pages(&[page_id]))?;
1023 deferred.pending.insert(page_id);
1026 return Ok(());
1027 }
1028 drop(deferred);
1029
1030 self.record_ownership(|wal| wal.retire_pages(&[page_id]))?;
1031
1032 self.cache.remove(page_id);
1034
1035 let mut freelist = self.freelist_write()?;
1037 freelist.free(page_id);
1038
1039 *self.header_dirty_lock()? = true;
1040
1041 Ok(())
1042 }
1043
1044 pub fn enable_deferred_reclamation(&self) -> Result<(), PagerError> {
1045 let _ownership = self.lock_ownership_mutations();
1046 self.deferred_reclamation
1047 .lock()
1048 .map_err(|_| PagerError::LockPoisoned)?
1049 .enabled = true;
1050 Ok(())
1051 }
1052
1053 pub fn deferred_reclamation_pages(&self) -> Result<(Vec<u32>, Vec<u32>), PagerError> {
1054 let deferred = self
1055 .deferred_reclamation
1056 .lock()
1057 .map_err(|_| PagerError::LockPoisoned)?;
1058 Ok((
1059 deferred.pending.iter().copied().collect(),
1060 deferred.published.iter().copied().collect(),
1061 ))
1062 }
1063
1064 pub fn restore_deferred_reclamation(&self, pages: &[u32]) -> Result<(), PagerError> {
1065 let _ownership = self.lock_ownership_mutations();
1066 let count = self.page_count()?;
1067 if pages
1068 .iter()
1069 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= count)
1070 {
1071 return Err(PagerError::InvalidDatabase(
1072 "invalid persisted deferred free page".into(),
1073 ));
1074 }
1075 let mut deferred = self
1076 .deferred_reclamation
1077 .lock()
1078 .map_err(|_| PagerError::LockPoisoned)?;
1079 deferred.pending.extend(pages);
1080 deferred.published.extend(pages);
1081 Ok(())
1082 }
1083
1084 pub fn complete_deferred_publication(
1087 &self,
1088 retained: &[u32],
1089 released: &[u32],
1090 ) -> Result<(), PagerError> {
1091 let _ownership = self.lock_ownership_mutations();
1092 let mut deferred = self
1093 .deferred_reclamation
1094 .lock()
1095 .map_err(|_| PagerError::LockPoisoned)?;
1096 if !released.is_empty() {
1097 self.free_retired_pages(released)?;
1098 }
1099 for id in released {
1100 deferred.pending.remove(id);
1101 }
1102 deferred.published = retained.iter().copied().collect();
1103 Ok(())
1104 }
1105
1106 pub fn record_retirement_intent(&self, pages: &[u32]) -> Result<(), PagerError> {
1108 let _ownership = self.lock_ownership_mutations();
1109 self.ensure_writeback_available()?;
1110 if self.config.read_only {
1111 return Err(PagerError::ReadOnly);
1112 }
1113 let page_count = self.page_count()?;
1114 let mut seen = std::collections::HashSet::with_capacity(pages.len());
1115 if pages
1116 .iter()
1117 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count || !seen.insert(*id))
1118 {
1119 return Err(PagerError::InvalidDatabase(
1120 "invalid retirement intent".into(),
1121 ));
1122 }
1123 self.record_ownership(|wal| wal.retire_pages(pages))
1124 }
1125
1126 pub fn free_retired_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
1129 let _ownership = self.lock_ownership_mutations();
1130 self.ensure_writeback_available()?;
1131 if self.config.read_only {
1132 return Err(PagerError::ReadOnly);
1133 }
1134 let page_count = self.page_count()?;
1135 if pages
1136 .iter()
1137 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count)
1138 {
1139 return Err(PagerError::InvalidDatabase(
1140 "invalid retired page range".into(),
1141 ));
1142 }
1143 let mut seen = std::collections::HashSet::with_capacity(pages.len());
1144 if pages.iter().any(|id| !seen.insert(*id)) {
1145 return Err(PagerError::InvalidDatabase("duplicate retired page".into()));
1146 }
1147 let mut freelist = self.freelist_write()?;
1148 if freelist.contains_any(&seen) {
1149 return Err(PagerError::InvalidDatabase(
1150 "retired page is already free".into(),
1151 ));
1152 }
1153 let mut trunk_id = freelist.trunk_head();
1157 let mut visited = std::collections::HashSet::new();
1158 while trunk_id != 0 && !seen.is_empty() {
1159 if trunk_id < FIRST_ALLOCATABLE_PAGE
1160 || trunk_id >= page_count
1161 || !visited.insert(trunk_id)
1162 {
1163 return Err(PagerError::InvalidDatabase(
1164 "invalid freelist trunk chain".into(),
1165 ));
1166 }
1167 let trunk = self.read_page(trunk_id)?;
1168 if trunk.page_id() != trunk_id {
1169 return Err(PagerError::InvalidDatabase(
1170 "misdirected freelist trunk".into(),
1171 ));
1172 }
1173 let mut loaded = FreeList::new();
1174 let next = loaded
1175 .load_from_trunk(&trunk)
1176 .map_err(|error| PagerError::InvalidDatabase(format!("Freelist: {error}")))?;
1177 while let Some(id) = loaded.allocate() {
1178 if id < FIRST_ALLOCATABLE_PAGE || id >= page_count {
1179 return Err(PagerError::InvalidDatabase(
1180 "invalid free page range".into(),
1181 ));
1182 }
1183 if seen.contains(&id) {
1184 return Err(PagerError::InvalidDatabase(
1185 "retired page is already free".into(),
1186 ));
1187 }
1188 }
1189 trunk_id = next.unwrap_or(0);
1190 }
1191 self.record_ownership(|wal| wal.retire_pages(pages))?;
1192 for id in pages {
1193 self.cache.remove(*id);
1194 }
1195 freelist.free_batch(pages);
1196 *self.header_dirty_lock()? = true;
1197 Ok(())
1198 }
1199
1200 fn pages_not_in_freelist(
1201 &self,
1202 pages: &[u32],
1203 freelist: &FreeList,
1204 page_count: u32,
1205 ) -> Result<std::collections::HashSet<u32>, PagerError> {
1206 let mut remaining = std::collections::HashSet::with_capacity(pages.len());
1207 if pages
1208 .iter()
1209 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count || !remaining.insert(*id))
1210 {
1211 return Err(PagerError::InvalidDatabase(
1212 "invalid recovery page set".into(),
1213 ));
1214 }
1215 let requested = remaining.clone();
1216 freelist
1217 .remove_existing_from(&requested, &mut remaining)
1218 .map_err(|error| PagerError::InvalidDatabase(format!("Freelist: {error}")))?;
1219 let mut trunk_id = freelist.trunk_head();
1220 let mut visited = std::collections::HashSet::new();
1221 while trunk_id != 0 {
1222 if trunk_id < FIRST_ALLOCATABLE_PAGE
1223 || trunk_id >= page_count
1224 || !visited.insert(trunk_id)
1225 {
1226 return Err(PagerError::InvalidDatabase(
1227 "invalid freelist trunk chain".into(),
1228 ));
1229 }
1230 let trunk = self.read_page(trunk_id)?;
1231 if trunk.page_id() != trunk_id {
1232 return Err(PagerError::InvalidDatabase(
1233 "misdirected freelist trunk".into(),
1234 ));
1235 }
1236 let mut loaded = FreeList::new();
1237 let next = loaded
1238 .load_from_trunk(&trunk)
1239 .map_err(|error| PagerError::InvalidDatabase(format!("Freelist: {error}")))?;
1240 while let Some(id) = loaded.allocate() {
1241 if id < FIRST_ALLOCATABLE_PAGE || id >= page_count {
1242 return Err(PagerError::InvalidDatabase(
1243 "invalid free page range".into(),
1244 ));
1245 }
1246 if requested.contains(&id) && !remaining.remove(&id) {
1247 return Err(PagerError::InvalidDatabase(
1248 "duplicate recovery page".into(),
1249 ));
1250 }
1251 }
1252 trunk_id = next.unwrap_or(0);
1253 }
1254 Ok(remaining)
1255 }
1256
1257 pub fn recover_unowned_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
1261 let _ownership = self.lock_ownership_mutations();
1262 self.ensure_writeback_available()?;
1263 if self.config.read_only {
1264 return Err(PagerError::ReadOnly);
1265 }
1266 let page_count = self.page_count()?;
1267 let mut freelist = self.freelist_write()?;
1268 let remaining = self.pages_not_in_freelist(pages, &freelist, page_count)?;
1269 if !remaining.is_empty() {
1270 let mut missing: Vec<_> = remaining.into_iter().collect();
1271 missing.sort_unstable();
1272 for id in &missing {
1273 self.cache.remove(*id);
1274 }
1275 freelist.free_batch(&missing);
1276 *self.header_dirty_lock()? = true;
1277 }
1278 Ok(())
1279 }
1280
1281 pub fn validate_owned_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
1284 let page_count = self.page_count()?;
1285 let freelist = self.freelist_write()?;
1286 let absent = self.pages_not_in_freelist(pages, &freelist, page_count)?;
1287 if absent.len() != pages.len() {
1288 return Err(PagerError::InvalidDatabase(
1289 "owned recovery page is free".into(),
1290 ));
1291 }
1292 Ok(())
1293 }
1294
1295 pub fn sync_recovered_allocator(&self) -> Result<(), PagerError> {
1298 let _ownership = self.lock_ownership_mutations();
1299 let result = (|| {
1300 self.sync()?;
1301 let _publication = self
1302 .superblock_publication
1303 .lock()
1304 .map_err(|_| PagerError::LockPoisoned)?;
1305 let header = self.header_read()?.clone();
1306 for _ in 0..reddb_file::PAGED_SUPERBLOCK_SLOT_COUNT {
1307 self.commit_superblock_locked(|image| {
1308 reddb_file::encode_database_header(image, &header)
1309 .map_err(|error| PagerError::InvalidDatabase(error.to_string()))
1310 })?;
1311 }
1312 Ok(())
1313 })();
1314 if result.is_err() {
1315 self.writeback_failed
1316 .store(true, std::sync::atomic::Ordering::Release);
1317 }
1318 result
1319 }
1320
1321 pub fn free_page_count(&self) -> Result<u32, PagerError> {
1324 Ok(self.freelist_write()?.total_free())
1325 }
1326
1327 pub fn header(&self) -> Result<DatabaseHeader, PagerError> {
1328 Ok(self.header_read()?.clone())
1329 }
1330
1331 pub fn physical_header(&self) -> Result<PhysicalFileHeader, PagerError> {
1332 Ok(self.header_read()?.physical)
1333 }
1334
1335 pub fn update_physical_header(&self, physical: PhysicalFileHeader) -> Result<(), PagerError> {
1336 let _ownership = self.lock_ownership_mutations();
1337 self.ensure_writeback_available()?;
1338 if self.config.read_only {
1339 return Err(PagerError::ReadOnly);
1340 }
1341
1342 let mut header = self.header_write()?;
1343 header.physical = physical;
1344 *self.header_dirty_lock()? = true;
1345 Ok(())
1346 }
1347
1348 pub fn page_count(&self) -> Result<u32, PagerError> {
1350 Ok(self.header_read()?.page_count)
1351 }
1352
1353 pub fn flush(&self) -> Result<(), PagerError> {
1355 let _ownership = self.lock_ownership_mutations();
1356 self.ensure_writeback_available()?;
1357 if self.config.read_only {
1358 return Ok(());
1359 }
1360
1361 let trunks = {
1363 let mut freelist = self.freelist_write()?;
1364 if freelist.is_dirty() {
1365 let mut header = self.header_write()?;
1366 let trunks = freelist.flush_to_trunks(0, || {
1367 let id = header.page_count;
1368 header.page_count += 1;
1369 id
1370 });
1371 header.freelist_head = freelist.trunk_head();
1372 *self.header_dirty_lock()? = true;
1373 freelist.mark_clean();
1374 trunks
1375 } else {
1376 Vec::new()
1377 }
1378 };
1379
1380 let mut dirty_pages = Vec::new();
1381 for trunk in trunks {
1382 let page_id = trunk.page_id();
1383 if let Some(evicted) = self.cache.insert_dirty(page_id, trunk) {
1384 dirty_pages.push((evicted.page_id(), evicted));
1387 }
1388 }
1389
1390 const FLUSH_BATCH_PAGES: usize = 256;
1394 for batch in dirty_pages.chunks(FLUSH_BATCH_PAGES) {
1395 self.write_dirty_page_batch(batch)?;
1396 }
1397 drop(dirty_pages);
1398 let mut page_ids = self.cache.dirty_page_ids(usize::MAX);
1402 page_ids.sort_unstable();
1403 for ids in page_ids.chunks(FLUSH_BATCH_PAGES) {
1404 let pages = self.cache.flush_dirty_pages(ids);
1405 self.write_dirty_page_batch(&pages)?;
1406 }
1407
1408 if *self.header_dirty_lock()? {
1410 self.write_header()?;
1411 }
1412
1413 Ok(())
1414 }
1415
1416 fn write_dirty_page_batch(&self, pages: &[(u32, Page)]) -> Result<(), PagerError> {
1417 if pages.is_empty() {
1418 return Ok(());
1419 }
1420 let result = (|| {
1424 let max_lsn = pages
1425 .iter()
1426 .filter_map(|(_, page)| page.header().ok().map(|header| header.lsn))
1427 .max()
1428 .unwrap_or(0);
1429 if max_lsn > 0 {
1430 self.wal.flush_until(max_lsn)?;
1431 }
1432 self.write_pages_through_dwb(pages)
1433 })();
1434 if result.is_err() {
1435 self.writeback_failed
1436 .store(true, std::sync::atomic::Ordering::Release);
1437 for (page_id, _) in pages {
1438 self.cache.mark_dirty(*page_id);
1439 }
1440 }
1441 result
1442 }
1443
1444 pub fn sync(&self) -> Result<(), PagerError> {
1446 let _ownership = self.lock_ownership_mutations();
1447 self.flush()?;
1448
1449 if self
1450 .allocation_reused
1451 .swap(false, std::sync::atomic::Ordering::AcqRel)
1452 {
1453 if let Err(error) = self.write_header_and_sync() {
1457 self.allocation_reused
1458 .store(true, std::sync::atomic::Ordering::Release);
1459 return Err(error);
1460 }
1461 }
1462
1463 let file = self.file_lock()?;
1464 file.sync_all()?;
1465
1466 Ok(())
1467 }
1468
1469 pub fn cache_stats(&self) -> super::page_cache::CacheStats {
1471 self.cache.stats()
1472 }
1473
1474 pub fn cache_len(&self) -> usize {
1477 self.cache.len()
1478 }
1479
1480 pub fn cache_capacity(&self) -> usize {
1483 self.cache.capacity()
1484 }
1485
1486 pub fn dirty_page_count(&self) -> usize {
1488 self.cache.dirty_count()
1489 }
1490
1491 pub fn dirty_fraction(&self) -> f64 {
1495 let capacity = self.cache.capacity().max(1) as f64;
1496 self.cache.dirty_count() as f64 / capacity
1497 }
1498
1499 pub fn flush_some_dirty(&self, max: usize) -> Result<usize, PagerError> {
1503 let _ownership = self.lock_ownership_mutations();
1504 self.ensure_writeback_available()?;
1505 if self.config.read_only || max == 0 {
1506 return Ok(0);
1507 }
1508 let dirty_pages = self.cache.flush_some_dirty(max);
1509 if dirty_pages.is_empty() {
1510 return Ok(0);
1511 }
1512 let count = dirty_pages.len();
1513 for (page_id, page) in dirty_pages {
1520 self.write_page(page_id, page)?;
1521 }
1522 Ok(count)
1523 }
1524
1525 pub fn path(&self) -> &Path {
1527 &self.path
1528 }
1529
1530 pub fn is_read_only(&self) -> bool {
1532 self.config.read_only
1533 }
1534
1535 pub fn file_size(&self) -> Result<u64, PagerError> {
1537 let file = self.file_lock()?;
1538 Ok(file.file_len()?)
1539 }
1540
1541 fn clear_dwb_zone(&self) -> Result<(), PagerError> {
1543 let mut file = self.file_lock()?;
1544 Ok(pager_io::clear_dwb_zone(&mut *file)?)
1545 }
1546
1547 pub fn read_manifest_page(&self) -> Result<Page, PagerError> {
1552 self.read_page(1)
1553 .map_err(|_| PagerError::ManifestZoneCorrupt(self.path.clone()))
1554 }
1555
1556 fn write_pages_through_dwb(&self, pages: &[(u32, Page)]) -> Result<(), PagerError> {
1563 if self.config.read_only {
1564 return Err(PagerError::ReadOnly);
1565 }
1566 let mut file = self.file_lock()?;
1569 self.ensure_writeback_available()?;
1570 let result = pager_io::write_pages(&mut *file, pages, self.dwb_enabled);
1571 if result.is_err() {
1572 self.writeback_failed
1575 .store(true, std::sync::atomic::Ordering::Release);
1576 }
1577 Ok(result?)
1578 }
1579
1580 fn recover_from_dwb(&self) -> Result<(), PagerError> {
1585 let mut file = self.file_lock()?;
1586 Ok(pager_io::recover_from_dwb(&mut *file)?)
1587 }
1588
1589 fn complete_allocation_fence(&self) -> Result<(), PagerError> {
1593 if self
1594 .allocation_fence_pending
1595 .load(std::sync::atomic::Ordering::Acquire)
1596 {
1597 self.write_header_and_sync()?;
1598 self.write_header_and_sync()?;
1599 self.allocation_fence_pending
1600 .store(false, std::sync::atomic::Ordering::Release);
1601 }
1602 Ok(())
1603 }
1604
1605 pub fn write_header_and_sync(&self) -> Result<(), PagerError> {
1607 let _ownership = self.lock_ownership_mutations();
1608 self.write_header()?;
1609 let file = self.file_lock()?;
1610 file.sync_all()?;
1611 Ok(())
1612 }
1613
1614 pub fn set_checkpoint_in_progress(
1616 &self,
1617 in_progress: bool,
1618 target_lsn: u64,
1619 ) -> Result<(), PagerError> {
1620 let _ownership = self.lock_ownership_mutations();
1621 let mut header = self.header_write()?;
1622 header.checkpoint_in_progress = in_progress;
1623 header.checkpoint_target_lsn = target_lsn;
1624 *self.header_dirty_lock()? = true;
1625 drop(header);
1626 self.write_header_and_sync()
1627 }
1628
1629 pub fn complete_checkpoint(&self, lsn: u64) -> Result<(), PagerError> {
1631 let _ownership = self.lock_ownership_mutations();
1632 let mut header = self.header_write()?;
1633 header.checkpoint_lsn = lsn;
1634 header.checkpoint_in_progress = false;
1635 header.checkpoint_target_lsn = 0;
1636 *self.header_dirty_lock()? = true;
1637 drop(header);
1638 self.write_header_and_sync()
1639 }
1640}