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 cache_size: usize,
161 pub read_only: bool,
162 pub verify_checksums: bool,
163 pub double_write: bool,
164}
165
166impl Default for PagerConfig {
167 fn default() -> Self {
168 Self {
169 cache_size: 10_000,
170 read_only: false,
171 verify_checksums: true,
172 double_write: true,
173 }
174 }
175}
176
177pub trait WalBarrier {
180 fn flush_until(&self, lsn: u64) -> std::io::Result<()>;
181
182 fn needs_page_image(&self, _page_id: u32) -> std::io::Result<bool> {
185 Ok(false)
186 }
187
188 fn capture_page_image(&self, _page: &Page) -> std::io::Result<()> {
191 Ok(())
192 }
193
194 fn reserve_pages(&self, _start: u32, _count: u32) -> std::io::Result<()> {
196 Ok(())
197 }
198
199 fn detach_free_pages(&self, _pages: &[u32]) -> std::io::Result<()> {
201 Ok(())
202 }
203
204 fn has_page_ownership_journal(&self) -> std::io::Result<bool> {
207 Ok(false)
208 }
209
210 fn retire_pages(&self, _pages: &[u32]) -> std::io::Result<()> {
212 Ok(())
213 }
214}
215
216pub struct NoWal;
218impl WalBarrier for NoWal {
219 fn flush_until(&self, _lsn: u64) -> std::io::Result<()> {
220 Err(std::io::Error::other(
221 "nonzero page LSN requires a WAL durability barrier",
222 ))
223 }
224}
225pub struct Pager<F: VfsFile, W: WalBarrier> {
226 path: PathBuf,
228 file: Mutex<F>,
230 dwb_enabled: bool,
232 writeback_failed: std::sync::atomic::AtomicBool,
235 cache: PageCache,
237 freelist: RwLock<FreeList>,
239 deferred_reclamation: Mutex<DeferredPageReclamation>,
242 ownership_mutations: parking_lot::ReentrantMutex<()>,
245 allocation_reused: std::sync::atomic::AtomicBool,
248 allocation_fence_pending: std::sync::atomic::AtomicBool,
250 header: RwLock<DatabaseHeader>,
252 superblock_publication: Mutex<()>,
254 config: PagerConfig,
256 header_dirty: Mutex<bool>,
258 wal: W,
259}
260
261#[derive(Default)]
262struct DeferredPageReclamation {
263 enabled: bool,
264 pending: std::collections::BTreeSet<u32>,
265 published: std::collections::BTreeSet<u32>,
266}
267
268const SUPERBLOCK_SLOT_SIZE: usize = reddb_file::PAGED_SUPERBLOCK_SLOT_SIZE;
270const SUPERBLOCK_ZONE_SIZE: usize = reddb_file::PAGED_SUPERBLOCK_ZONE_SIZE;
271const PHASE3_DWB_ZONE_FILE_VERSION: u32 = 0x0001_0001;
274
275pub type NewestSuperblock = (
277 reddb_file::PagedSuperblockSelection,
278 [u8; SUPERBLOCK_SLOT_SIZE],
279);
280
281fn superblock_slot_image(page: &[u8; PAGE_SIZE]) -> [u8; SUPERBLOCK_SLOT_SIZE] {
284 let mut image = [0u8; SUPERBLOCK_SLOT_SIZE];
285 image.copy_from_slice(&page[..SUPERBLOCK_SLOT_SIZE]);
286 image
287}
288
289impl<F: VfsFile, W: WalBarrier> Pager<F, W> {
290 pub fn open_owned(
294 file: F,
295 path: PathBuf,
296 config: PagerConfig,
297 wal: W,
298 create: bool,
299 ) -> Result<Self, PagerError> {
300 if create && file.file_len()? != 0 {
301 return Err(PagerError::InvalidDatabase(
302 "new pager file is not empty".into(),
303 ));
304 }
305 let pager = Self {
306 path,
307 file: Mutex::new(file),
308 dwb_enabled: config.double_write && !config.read_only,
309 writeback_failed: std::sync::atomic::AtomicBool::new(false),
310 cache: PageCache::new(config.cache_size),
311 freelist: RwLock::new(FreeList::new()),
312 deferred_reclamation: Mutex::new(DeferredPageReclamation::default()),
313 ownership_mutations: parking_lot::ReentrantMutex::new(()),
314 allocation_reused: std::sync::atomic::AtomicBool::new(false),
315 allocation_fence_pending: std::sync::atomic::AtomicBool::new(false),
316 header: RwLock::new(DatabaseHeader::default()),
317 superblock_publication: Mutex::new(()),
318 config,
319 header_dirty: Mutex::new(false),
320 wal,
321 };
322 if create {
323 pager.initialize()?;
324 } else {
325 pager.ensure_phase3_dwb_zone_layout()?;
326 pager.recover_from_dwb()?;
327 pager.load_header()?;
328 }
329 Ok(pager)
330 }
331
332 pub fn wal_barrier(&self) -> &W {
333 &self.wal
334 }
335
336 pub fn lock_ownership_mutations(&self) -> parking_lot::ReentrantMutexGuard<'_, ()> {
339 self.ownership_mutations.lock()
340 }
341
342 pub fn fence_interrupted_ownership(&self) {
344 self.writeback_failed
345 .store(true, std::sync::atomic::Ordering::Release);
346 }
347
348 #[doc(hidden)]
351 pub fn with_file<T>(&self, operation: impl FnOnce(&mut F) -> T) -> Result<T, PagerError> {
352 Ok(operation(&mut *self.file_lock()?))
353 }
354
355 fn initialize(&self) -> Result<(), PagerError> {
357 if self.config.read_only {
358 return Err(PagerError::ReadOnly);
359 }
360
361 let initial_page_count = FIRST_ALLOCATABLE_PAGE;
365 let header_page = Page::new_header_page(initial_page_count);
366 self.header_write()?.page_count = initial_page_count;
367
368 self.write_page_raw(0, &header_page)?;
373 let mut metadata_page = Page::new(PageType::Header, 1);
374 metadata_page.update_checksum();
375 self.write_page_raw(1, &metadata_page)?;
376 let mut vault_page = Page::new(PageType::Vault, 2);
377 vault_page.update_checksum();
378 self.write_page_raw(2, &vault_page)?;
379 self.clear_dwb_zone()?;
380
381 let mut slot = superblock_slot_image(header_page.as_bytes());
385 self.write_superblock_slot(&mut slot, 0, 1)?;
386 self.write_superblock_slot(&mut slot, 1, 2)?;
387
388 self.sync()?;
390
391 Ok(())
392 }
393
394 fn header_write(&self) -> Result<std::sync::RwLockWriteGuard<'_, DatabaseHeader>, PagerError> {
396 self.header.write().map_err(|_| PagerError::LockPoisoned)
397 }
398
399 fn header_read(&self) -> Result<std::sync::RwLockReadGuard<'_, DatabaseHeader>, PagerError> {
401 self.header.read().map_err(|_| PagerError::LockPoisoned)
402 }
403
404 fn freelist_write(&self) -> Result<std::sync::RwLockWriteGuard<'_, FreeList>, PagerError> {
406 self.freelist.write().map_err(|_| PagerError::LockPoisoned)
407 }
408
409 fn file_lock(&self) -> Result<std::sync::MutexGuard<'_, F>, PagerError> {
411 self.file.lock().map_err(|_| PagerError::LockPoisoned)
412 }
413
414 fn header_dirty_lock(&self) -> Result<std::sync::MutexGuard<'_, bool>, PagerError> {
416 self.header_dirty
417 .lock()
418 .map_err(|_| PagerError::LockPoisoned)
419 }
420
421 fn read_superblock_zone(&self) -> Result<[u8; SUPERBLOCK_ZONE_SIZE], PagerError> {
425 let mut file = self.file_lock()?;
426 Ok(pager_io::read_superblock_zone(&mut *file)?)
427 }
428
429 #[doc(hidden)]
435 pub fn newest_superblock(&self) -> Result<NewestSuperblock, PagerError> {
436 let zone = self.read_superblock_zone()?;
437 let selection = reddb_file::select_paged_superblock(&zone)
438 .ok_or_else(|| PagerError::SuperblockZoneUnrecoverable(self.path.clone()))?;
439
440 let start = selection.copy_index * SUPERBLOCK_SLOT_SIZE;
441 let mut image = [0u8; SUPERBLOCK_SLOT_SIZE];
442 image.copy_from_slice(&zone[start..start + SUPERBLOCK_SLOT_SIZE]);
443 Ok((selection, image))
444 }
445
446 fn ensure_phase3_dwb_zone_layout(&self) -> Result<(), PagerError> {
447 let (_, image) = self.newest_superblock()?;
448 let header = reddb_file::decode_database_header(&image)
449 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
450 if header.version < PHASE3_DWB_ZONE_FILE_VERSION {
451 return Err(PagerError::LegacyPagedStore {
452 path: self.path.clone(),
453 });
454 }
455 Ok(())
456 }
457
458 fn write_superblock_slot(
461 &self,
462 slot: &mut [u8; SUPERBLOCK_SLOT_SIZE],
463 copy_index: usize,
464 generation: u64,
465 ) -> Result<(), PagerError> {
466 self.ensure_writeback_available()?;
467 if self.config.read_only {
468 return Err(PagerError::ReadOnly);
469 }
470 let mut file = self.file_lock()?;
471 self.ensure_writeback_available()?;
472 Ok(pager_io::write_superblock_slot(
473 &mut *file, slot, copy_index, generation,
474 )?)
475 }
476
477 #[doc(hidden)]
483 pub fn commit_superblock(
484 &self,
485 mutate: impl FnOnce(&mut [u8; SUPERBLOCK_SLOT_SIZE]) -> Result<(), PagerError>,
486 ) -> Result<(), PagerError> {
487 let _ownership = self.lock_ownership_mutations();
488 let _publication = self
489 .superblock_publication
490 .lock()
491 .map_err(|_| PagerError::LockPoisoned)?;
492 self.commit_superblock_locked(mutate)
493 }
494
495 fn commit_superblock_locked(
496 &self,
497 mutate: impl FnOnce(&mut [u8; SUPERBLOCK_SLOT_SIZE]) -> Result<(), PagerError>,
498 ) -> Result<(), PagerError> {
499 if self.config.read_only {
500 return Err(PagerError::ReadOnly);
501 }
502 let (newest, mut image) = self.newest_superblock()?;
503 mutate(&mut image)?;
504 let next_copy = reddb_file::paged_superblock_next_copy(Some(newest));
505 self.write_superblock_slot(&mut image, next_copy, newest.generation.saturating_add(1))
506 }
507
508 pub fn fence_file_format_version(&self, version: u32) -> Result<(), PagerError> {
514 let _ownership = self.lock_ownership_mutations();
515 self.ensure_writeback_available()?;
516 if self.config.read_only {
517 return Err(PagerError::ReadOnly);
518 }
519 if !(PHASE3_DWB_ZONE_FILE_VERSION..=reddb_file::PAGE_FILE_VERSION).contains(&version) {
520 return Err(PagerError::InvalidDatabase(
521 "unsupported file format fence".into(),
522 ));
523 }
524 let _publication = self
525 .superblock_publication
526 .lock()
527 .map_err(|_| PagerError::LockPoisoned)?;
528 if version < self.header_read()?.version {
529 return Err(PagerError::InvalidDatabase(
530 "file format downgrade is not supported".into(),
531 ));
532 }
533 let result = (|| {
534 for _ in 0..reddb_file::PAGED_SUPERBLOCK_SLOT_COUNT {
535 self.commit_superblock_locked(|image| {
536 reddb_file::set_database_header_version(image, version)
537 .map_err(|error| PagerError::InvalidDatabase(error.to_string()))
538 })?;
539 }
540 self.header_write()?.version = version;
541 Ok(())
542 })();
543 if result.is_err() {
544 self.writeback_failed
545 .store(true, std::sync::atomic::Ordering::Release);
546 }
547 result
548 }
549
550 fn load_header(&self) -> Result<(), PagerError> {
552 let (_, image) = self.newest_superblock()?;
553
554 let mut decoded_header = reddb_file::decode_database_header(&image)
555 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
556 let physical_pages = self.file_lock()?.file_len()?.div_ceil(PAGE_SIZE as u64);
562 let physical_pages = u32::try_from(physical_pages).map_err(|_| {
563 PagerError::InvalidDatabase("data file exceeds the page address space".into())
564 })?;
565 if decoded_header.page_count < physical_pages {
566 decoded_header.page_count = physical_pages;
567 if !self.config.read_only {
568 *self.header_dirty_lock()? = true;
569 }
570 }
571 let freelist_head = decoded_header.freelist_head;
572
573 {
574 let mut header = self.header_write()?;
575 *header = decoded_header;
576 }
577
578 {
580 let mut freelist = self.freelist_write()?;
581 *freelist = FreeList::from_header(freelist_head, 0);
582 }
583
584 Ok(())
585 }
586
587 fn write_header(&self) -> Result<(), PagerError> {
589 if self.config.read_only {
590 return Err(PagerError::ReadOnly);
591 }
592
593 let header = self.header_read()?.clone();
594 self.commit_superblock(|image| {
595 reddb_file::encode_database_header(image, &header)
596 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))
597 })?;
598 *self.header_dirty_lock()? = false;
599
600 Ok(())
601 }
602
603 fn read_page_raw(&self, page_id: u32) -> Result<Page, PagerError> {
605 let mut file = self.file_lock()?;
606 Ok(pager_io::read_page(
607 &mut *file,
608 page_id,
609 self.config.verify_checksums,
610 )?)
611 }
612
613 fn ensure_writeback_available(&self) -> Result<(), PagerError> {
614 if self
615 .writeback_failed
616 .load(std::sync::atomic::Ordering::Acquire)
617 {
618 return Err(PagerError::Io(std::io::Error::other(
619 "page writeback failed; reopen the database to recover before writing",
620 )));
621 }
622 Ok(())
623 }
624
625 fn record_ownership(
626 &self,
627 operation: impl FnOnce(&W) -> std::io::Result<()>,
628 ) -> Result<(), PagerError> {
629 if let Err(error) = operation(&self.wal) {
630 self.writeback_failed
631 .store(true, std::sync::atomic::Ordering::Release);
632 return Err(PagerError::Io(error));
633 }
634 Ok(())
635 }
636
637 fn write_evicted_dirty_page(&self, page: Page) -> Result<(), PagerError> {
640 let mut ids = self
641 .cache
642 .dirty_page_ids(pager_io::DWB_MAX_PAGES_PER_FRAME.saturating_sub(1));
643 ids.sort_unstable();
644 let mut pages = self.cache.flush_dirty_pages(&ids);
645 pages.push((page.page_id(), page));
646 pages.sort_unstable_by_key(|(id, _)| *id);
647 self.write_dirty_page_batch(&pages)
648 }
649
650 fn write_page_raw(&self, page_id: u32, page: &Page) -> Result<(), PagerError> {
652 self.ensure_writeback_available()?;
653 if self.config.read_only {
654 return Err(PagerError::ReadOnly);
655 }
656 let mut file = self.file_lock()?;
657 self.ensure_writeback_available()?;
658 Ok(pager_io::write_page(&mut *file, page_id, page)?)
659 }
660
661 pub fn read_page(&self, page_id: u32) -> Result<Page, PagerError> {
663 if let Some(page) = self.cache.get(page_id) {
665 return Ok(page);
666 }
667
668 let page = self.read_page_raw(page_id)?;
670
671 if let Some(dirty_page) = self.cache.insert(page_id, page.clone()) {
673 self.write_evicted_dirty_page(dirty_page)?;
675 }
676
677 Ok(page)
678 }
679
680 pub fn read_page_no_checksum(&self, page_id: u32) -> Result<Page, PagerError> {
685 if let Some(page) = self.cache.get(page_id) {
687 return Ok(page);
688 }
689
690 let mut file = self.file_lock()?;
692 let page = pager_io::read_page(&mut *file, page_id, false)?;
693 drop(file);
694
695 if let Some(dirty_page) = self.cache.insert(page_id, page.clone()) {
697 self.write_evicted_dirty_page(dirty_page)?;
699 }
700
701 Ok(page)
702 }
703
704 pub fn write_page(&self, page_id: u32, mut page: Page) -> Result<(), PagerError> {
706 let _ownership = self.lock_ownership_mutations();
707 self.ensure_writeback_available()?;
708 if self.config.read_only {
709 return Err(PagerError::ReadOnly);
710 }
711
712 self.capture_page_before_write(page_id)?;
713
714 page.update_checksum();
716
717 if let Some(dirty_page) = self.cache.insert(page_id, page) {
722 self.write_evicted_dirty_page(dirty_page)?;
723 }
724 self.cache.mark_dirty(page_id);
725
726 Ok(())
727 }
728
729 pub fn write_page_no_checksum(&self, page_id: u32, page: Page) -> Result<(), PagerError> {
734 let _ownership = self.lock_ownership_mutations();
735 self.ensure_writeback_available()?;
736 if self.config.read_only {
737 return Err(PagerError::ReadOnly);
738 }
739
740 self.capture_page_before_write(page_id)?;
741
742 if let Some(dirty_page) = self.cache.insert(page_id, page) {
745 self.write_evicted_dirty_page(dirty_page)?;
746 }
747 self.cache.mark_dirty(page_id);
748
749 Ok(())
750 }
751
752 fn capture_page_before_write(&self, page_id: u32) -> Result<(), PagerError> {
753 let mut required = false;
754 self.record_ownership(|wal| {
755 required = wal.needs_page_image(page_id)?;
756 Ok(())
757 })?;
758 if required {
759 let previous = self.read_page(page_id)?;
760 self.record_ownership(|wal| wal.capture_page_image(&previous))?;
761 }
762 Ok(())
763 }
764
765 pub fn allocate_page(&self, page_type: PageType) -> Result<Page, PagerError> {
767 let _ownership = self.lock_ownership_mutations();
768 self.ensure_writeback_available()?;
769 if self.config.read_only {
770 return Err(PagerError::ReadOnly);
771 }
772
773 let page_id = {
775 let mut freelist = self.freelist_write()?;
776 self.complete_allocation_fence()?;
779 if let Some(id) = freelist.next_in_memory_page() {
780 self.record_ownership(|wal| wal.reserve_pages(id, 1))?;
781 let id = freelist
782 .allocate()
783 .expect("free page checked while allocator is locked");
784 self.allocation_reused
785 .store(true, std::sync::atomic::Ordering::Release);
786 id
787 } else if freelist.trunk_head() != 0 {
788 let trunk_id = freelist.trunk_head();
789
790 let trunk = self.read_page(trunk_id).map_err(|e| match e {
791 PagerError::PageNotFound(_) => {
792 PagerError::InvalidDatabase("Freelist trunk missing".to_string())
793 }
794 other => other,
795 })?;
796 if trunk.page_id() != trunk_id {
797 return Err(PagerError::InvalidDatabase(
798 "misdirected freelist trunk".into(),
799 ));
800 }
801
802 let mut detached = FreeList::new();
805 detached
806 .load_from_trunk(&trunk)
807 .map_err(|e| PagerError::InvalidDatabase(format!("Freelist: {e}")))?;
808 let mut pages = Vec::new();
809 let mut seen = std::collections::HashSet::new();
810 let page_count = self.page_count()?;
811 while let Some(id) = detached.allocate() {
812 if id < FIRST_ALLOCATABLE_PAGE || id >= page_count || !seen.insert(id) {
813 return Err(PagerError::InvalidDatabase(
814 "invalid freelist trunk page".into(),
815 ));
816 }
817 pages.push(id);
818 }
819 let mut journaled = false;
820 self.record_ownership(|wal| {
821 journaled = wal.has_page_ownership_journal()?;
822 Ok(())
823 })?;
824 if !journaled {
825 self.allocate_unjournaled_trunk(&mut freelist, trunk, &pages)?
826 } else {
827 self.record_ownership(|wal| wal.detach_free_pages(&pages))?;
828
829 freelist
830 .load_from_trunk(&trunk)
831 .map_err(|e| PagerError::InvalidDatabase(format!("Freelist: {}", e)))?;
832 let mut header = self.header_write()?;
833 header.freelist_head = freelist.trunk_head();
834 *self.header_dirty_lock()? = true;
835 drop(header);
836 self.allocation_fence_pending
841 .store(true, std::sync::atomic::Ordering::Release);
842 self.complete_allocation_fence()?;
843 let next = freelist.next_in_memory_page().ok_or_else(|| {
844 PagerError::InvalidDatabase("Freelist empty after trunk load".to_string())
845 })?;
846 self.record_ownership(|wal| wal.reserve_pages(next, 1))?;
847 let id = freelist.allocate().ok_or_else(|| {
848 PagerError::InvalidDatabase("Freelist empty after trunk load".to_string())
849 })?;
850 self.allocation_reused
851 .store(true, std::sync::atomic::Ordering::Release);
852 id
853 }
854 } else {
855 let mut header = self.header_write()?;
857 let id = header.page_count;
858 let next = id
859 .checked_add(1)
860 .ok_or_else(|| PagerError::InvalidDatabase("page count overflow".into()))?;
861 self.record_ownership(|wal| wal.reserve_pages(id, 1))?;
862 header.page_count = next;
863 *self.header_dirty_lock()? = true;
864 id
865 }
866 };
867
868 let page = Page::new(page_type, page_id);
869
870 if let Some(dirty_page) = self.cache.insert(page_id, page.clone()) {
882 self.write_evicted_dirty_page(dirty_page)?;
883 }
884 self.cache.mark_dirty(page_id);
885
886 Ok(page)
887 }
888
889 fn allocate_unjournaled_trunk(
893 &self,
894 freelist: &mut FreeList,
895 mut trunk: Page,
896 pages: &[u32],
897 ) -> Result<u32, PagerError> {
898 let id = *pages
899 .first()
900 .expect("validated trunk includes its own page");
901 self.record_ownership(|wal| wal.reserve_pages(id, 1))?;
902 if pages.len() > 1 {
903 let remaining =
904 u32::try_from(pages.len() - 2).expect("a validated trunk fits in one page");
905 trunk.content_mut()[4..8].copy_from_slice(&remaining.to_le_bytes());
906 trunk.update_checksum();
907 self.cache.remove(trunk.page_id());
908 self.write_pages_through_dwb(&[(trunk.page_id(), trunk)])?;
909 freelist.note_durable_trunk_allocation(remaining);
910 } else {
911 freelist
914 .load_from_trunk(&trunk)
915 .map_err(|error| PagerError::InvalidDatabase(error.to_string()))?;
916 self.header_write()?.freelist_head = freelist.trunk_head();
917 *self.header_dirty_lock()? = true;
918 self.allocation_fence_pending
919 .store(true, std::sync::atomic::Ordering::Release);
920 self.complete_allocation_fence()?;
921 assert_eq!(freelist.allocate(), Some(id));
922 }
923 self.allocation_reused
924 .store(true, std::sync::atomic::Ordering::Release);
925 Ok(id)
926 }
927
928 pub fn reserve_contig_extent(&self, n_pages: u32) -> Result<ExtentId, PagerError> {
930 let _ownership = self.lock_ownership_mutations();
931 self.ensure_writeback_available()?;
932 if self.config.read_only {
933 return Err(PagerError::ReadOnly);
934 }
935 if n_pages == 0 {
936 return Err(PagerError::InvalidDatabase(
937 "contiguous extent must reserve at least one page".to_string(),
938 ));
939 }
940
941 let start_page = {
942 let mut header = self.header_write()?;
943 let start = header.page_count;
944 let end = header.page_count.checked_add(n_pages).ok_or_else(|| {
945 PagerError::InvalidDatabase("contiguous extent page count overflow".to_string())
946 })?;
947 self.record_ownership(|wal| wal.reserve_pages(start, n_pages))?;
948 header.page_count = end;
949 *self.header_dirty_lock()? = true;
950 start
951 };
952
953 for page_id in start_page..start_page + n_pages {
954 let mut page = Page::new(PageType::Vector, page_id);
955 page.update_checksum();
956 if let Some(dirty_page) = self.cache.insert(page_id, page) {
957 self.write_evicted_dirty_page(dirty_page)?;
958 }
959 self.cache.mark_dirty(page_id);
960 }
961
962 Ok(ExtentId {
963 start_page,
964 n_pages,
965 })
966 }
967
968 pub fn free_page(&self, page_id: u32) -> Result<(), PagerError> {
970 let _ownership = self.lock_ownership_mutations();
971 self.ensure_writeback_available()?;
972 if self.config.read_only {
973 return Err(PagerError::ReadOnly);
974 }
975
976 let mut deferred = self
977 .deferred_reclamation
978 .lock()
979 .map_err(|_| PagerError::LockPoisoned)?;
980 if deferred.enabled {
981 if page_id < FIRST_ALLOCATABLE_PAGE || page_id >= self.page_count()? {
982 return Err(PagerError::InvalidDatabase(
983 "invalid deferred free page".into(),
984 ));
985 }
986 self.record_ownership(|wal| wal.retire_pages(&[page_id]))?;
987 deferred.pending.insert(page_id);
990 return Ok(());
991 }
992 drop(deferred);
993
994 self.record_ownership(|wal| wal.retire_pages(&[page_id]))?;
995
996 self.cache.remove(page_id);
998
999 let mut freelist = self.freelist_write()?;
1001 freelist.free(page_id);
1002
1003 *self.header_dirty_lock()? = true;
1004
1005 Ok(())
1006 }
1007
1008 pub fn enable_deferred_reclamation(&self) -> Result<(), PagerError> {
1009 let _ownership = self.lock_ownership_mutations();
1010 self.deferred_reclamation
1011 .lock()
1012 .map_err(|_| PagerError::LockPoisoned)?
1013 .enabled = true;
1014 Ok(())
1015 }
1016
1017 pub fn deferred_reclamation_pages(&self) -> Result<(Vec<u32>, Vec<u32>), PagerError> {
1018 let deferred = self
1019 .deferred_reclamation
1020 .lock()
1021 .map_err(|_| PagerError::LockPoisoned)?;
1022 Ok((
1023 deferred.pending.iter().copied().collect(),
1024 deferred.published.iter().copied().collect(),
1025 ))
1026 }
1027
1028 pub fn restore_deferred_reclamation(&self, pages: &[u32]) -> Result<(), PagerError> {
1029 let _ownership = self.lock_ownership_mutations();
1030 let count = self.page_count()?;
1031 if pages
1032 .iter()
1033 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= count)
1034 {
1035 return Err(PagerError::InvalidDatabase(
1036 "invalid persisted deferred free page".into(),
1037 ));
1038 }
1039 let mut deferred = self
1040 .deferred_reclamation
1041 .lock()
1042 .map_err(|_| PagerError::LockPoisoned)?;
1043 deferred.pending.extend(pages);
1044 deferred.published.extend(pages);
1045 Ok(())
1046 }
1047
1048 pub fn complete_deferred_publication(
1051 &self,
1052 retained: &[u32],
1053 released: &[u32],
1054 ) -> Result<(), PagerError> {
1055 let _ownership = self.lock_ownership_mutations();
1056 let mut deferred = self
1057 .deferred_reclamation
1058 .lock()
1059 .map_err(|_| PagerError::LockPoisoned)?;
1060 if !released.is_empty() {
1061 self.free_retired_pages(released)?;
1062 }
1063 for id in released {
1064 deferred.pending.remove(id);
1065 }
1066 deferred.published = retained.iter().copied().collect();
1067 Ok(())
1068 }
1069
1070 pub fn record_retirement_intent(&self, pages: &[u32]) -> Result<(), PagerError> {
1072 let _ownership = self.lock_ownership_mutations();
1073 self.ensure_writeback_available()?;
1074 if self.config.read_only {
1075 return Err(PagerError::ReadOnly);
1076 }
1077 let page_count = self.page_count()?;
1078 let mut seen = std::collections::HashSet::with_capacity(pages.len());
1079 if pages
1080 .iter()
1081 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count || !seen.insert(*id))
1082 {
1083 return Err(PagerError::InvalidDatabase(
1084 "invalid retirement intent".into(),
1085 ));
1086 }
1087 self.record_ownership(|wal| wal.retire_pages(pages))
1088 }
1089
1090 pub fn free_retired_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
1093 let _ownership = self.lock_ownership_mutations();
1094 self.ensure_writeback_available()?;
1095 if self.config.read_only {
1096 return Err(PagerError::ReadOnly);
1097 }
1098 let page_count = self.page_count()?;
1099 if pages
1100 .iter()
1101 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count)
1102 {
1103 return Err(PagerError::InvalidDatabase(
1104 "invalid retired page range".into(),
1105 ));
1106 }
1107 let mut seen = std::collections::HashSet::with_capacity(pages.len());
1108 if pages.iter().any(|id| !seen.insert(*id)) {
1109 return Err(PagerError::InvalidDatabase("duplicate retired page".into()));
1110 }
1111 let mut freelist = self.freelist_write()?;
1112 if freelist.contains_any(&seen) {
1113 return Err(PagerError::InvalidDatabase(
1114 "retired page is already free".into(),
1115 ));
1116 }
1117 let mut trunk_id = freelist.trunk_head();
1121 let mut visited = std::collections::HashSet::new();
1122 while trunk_id != 0 && !seen.is_empty() {
1123 if trunk_id < FIRST_ALLOCATABLE_PAGE
1124 || trunk_id >= page_count
1125 || !visited.insert(trunk_id)
1126 {
1127 return Err(PagerError::InvalidDatabase(
1128 "invalid freelist trunk chain".into(),
1129 ));
1130 }
1131 let trunk = self.read_page(trunk_id)?;
1132 if trunk.page_id() != trunk_id {
1133 return Err(PagerError::InvalidDatabase(
1134 "misdirected freelist trunk".into(),
1135 ));
1136 }
1137 let mut loaded = FreeList::new();
1138 let next = loaded
1139 .load_from_trunk(&trunk)
1140 .map_err(|error| PagerError::InvalidDatabase(format!("Freelist: {error}")))?;
1141 while let Some(id) = loaded.allocate() {
1142 if id < FIRST_ALLOCATABLE_PAGE || id >= page_count {
1143 return Err(PagerError::InvalidDatabase(
1144 "invalid free page range".into(),
1145 ));
1146 }
1147 if seen.contains(&id) {
1148 return Err(PagerError::InvalidDatabase(
1149 "retired page is already free".into(),
1150 ));
1151 }
1152 }
1153 trunk_id = next.unwrap_or(0);
1154 }
1155 self.record_ownership(|wal| wal.retire_pages(pages))?;
1156 for id in pages {
1157 self.cache.remove(*id);
1158 }
1159 freelist.free_batch(pages);
1160 *self.header_dirty_lock()? = true;
1161 Ok(())
1162 }
1163
1164 fn pages_not_in_freelist(
1165 &self,
1166 pages: &[u32],
1167 freelist: &FreeList,
1168 page_count: u32,
1169 ) -> Result<std::collections::HashSet<u32>, PagerError> {
1170 let mut remaining = std::collections::HashSet::with_capacity(pages.len());
1171 if pages
1172 .iter()
1173 .any(|id| *id < FIRST_ALLOCATABLE_PAGE || *id >= page_count || !remaining.insert(*id))
1174 {
1175 return Err(PagerError::InvalidDatabase(
1176 "invalid recovery page set".into(),
1177 ));
1178 }
1179 let requested = remaining.clone();
1180 freelist
1181 .remove_existing_from(&requested, &mut remaining)
1182 .map_err(|error| PagerError::InvalidDatabase(format!("Freelist: {error}")))?;
1183 let mut trunk_id = freelist.trunk_head();
1184 let mut visited = std::collections::HashSet::new();
1185 while trunk_id != 0 {
1186 if trunk_id < FIRST_ALLOCATABLE_PAGE
1187 || trunk_id >= page_count
1188 || !visited.insert(trunk_id)
1189 {
1190 return Err(PagerError::InvalidDatabase(
1191 "invalid freelist trunk chain".into(),
1192 ));
1193 }
1194 let trunk = self.read_page(trunk_id)?;
1195 if trunk.page_id() != trunk_id {
1196 return Err(PagerError::InvalidDatabase(
1197 "misdirected freelist trunk".into(),
1198 ));
1199 }
1200 let mut loaded = FreeList::new();
1201 let next = loaded
1202 .load_from_trunk(&trunk)
1203 .map_err(|error| PagerError::InvalidDatabase(format!("Freelist: {error}")))?;
1204 while let Some(id) = loaded.allocate() {
1205 if id < FIRST_ALLOCATABLE_PAGE || id >= page_count {
1206 return Err(PagerError::InvalidDatabase(
1207 "invalid free page range".into(),
1208 ));
1209 }
1210 if requested.contains(&id) && !remaining.remove(&id) {
1211 return Err(PagerError::InvalidDatabase(
1212 "duplicate recovery page".into(),
1213 ));
1214 }
1215 }
1216 trunk_id = next.unwrap_or(0);
1217 }
1218 Ok(remaining)
1219 }
1220
1221 pub fn recover_unowned_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
1225 let _ownership = self.lock_ownership_mutations();
1226 self.ensure_writeback_available()?;
1227 if self.config.read_only {
1228 return Err(PagerError::ReadOnly);
1229 }
1230 let page_count = self.page_count()?;
1231 let mut freelist = self.freelist_write()?;
1232 let remaining = self.pages_not_in_freelist(pages, &freelist, page_count)?;
1233 if !remaining.is_empty() {
1234 let mut missing: Vec<_> = remaining.into_iter().collect();
1235 missing.sort_unstable();
1236 for id in &missing {
1237 self.cache.remove(*id);
1238 }
1239 freelist.free_batch(&missing);
1240 *self.header_dirty_lock()? = true;
1241 }
1242 Ok(())
1243 }
1244
1245 pub fn validate_owned_pages(&self, pages: &[u32]) -> Result<(), PagerError> {
1248 let page_count = self.page_count()?;
1249 let freelist = self.freelist_write()?;
1250 let absent = self.pages_not_in_freelist(pages, &freelist, page_count)?;
1251 if absent.len() != pages.len() {
1252 return Err(PagerError::InvalidDatabase(
1253 "owned recovery page is free".into(),
1254 ));
1255 }
1256 Ok(())
1257 }
1258
1259 pub fn sync_recovered_allocator(&self) -> Result<(), PagerError> {
1262 let _ownership = self.lock_ownership_mutations();
1263 let result = (|| {
1264 self.sync()?;
1265 let _publication = self
1266 .superblock_publication
1267 .lock()
1268 .map_err(|_| PagerError::LockPoisoned)?;
1269 let header = self.header_read()?.clone();
1270 for _ in 0..reddb_file::PAGED_SUPERBLOCK_SLOT_COUNT {
1271 self.commit_superblock_locked(|image| {
1272 reddb_file::encode_database_header(image, &header)
1273 .map_err(|error| PagerError::InvalidDatabase(error.to_string()))
1274 })?;
1275 }
1276 Ok(())
1277 })();
1278 if result.is_err() {
1279 self.writeback_failed
1280 .store(true, std::sync::atomic::Ordering::Release);
1281 }
1282 result
1283 }
1284
1285 pub fn free_page_count(&self) -> Result<u32, PagerError> {
1288 Ok(self.freelist_write()?.total_free())
1289 }
1290
1291 pub fn header(&self) -> Result<DatabaseHeader, PagerError> {
1292 Ok(self.header_read()?.clone())
1293 }
1294
1295 pub fn physical_header(&self) -> Result<PhysicalFileHeader, PagerError> {
1296 Ok(self.header_read()?.physical)
1297 }
1298
1299 pub fn update_physical_header(&self, physical: PhysicalFileHeader) -> Result<(), PagerError> {
1300 let _ownership = self.lock_ownership_mutations();
1301 self.ensure_writeback_available()?;
1302 if self.config.read_only {
1303 return Err(PagerError::ReadOnly);
1304 }
1305
1306 let mut header = self.header_write()?;
1307 header.physical = physical;
1308 *self.header_dirty_lock()? = true;
1309 Ok(())
1310 }
1311
1312 pub fn page_count(&self) -> Result<u32, PagerError> {
1314 Ok(self.header_read()?.page_count)
1315 }
1316
1317 pub fn flush(&self) -> Result<(), PagerError> {
1319 let _ownership = self.lock_ownership_mutations();
1320 self.ensure_writeback_available()?;
1321 if self.config.read_only {
1322 return Ok(());
1323 }
1324
1325 let trunks = {
1327 let mut freelist = self.freelist_write()?;
1328 if freelist.is_dirty() {
1329 let mut header = self.header_write()?;
1330 let trunks = freelist.flush_to_trunks(0, || {
1331 let id = header.page_count;
1332 header.page_count += 1;
1333 id
1334 });
1335 header.freelist_head = freelist.trunk_head();
1336 *self.header_dirty_lock()? = true;
1337 freelist.mark_clean();
1338 trunks
1339 } else {
1340 Vec::new()
1341 }
1342 };
1343
1344 let mut dirty_pages = Vec::new();
1345 for trunk in trunks {
1346 let page_id = trunk.page_id();
1347 if let Some(evicted) = self.cache.insert(page_id, trunk) {
1348 dirty_pages.push((evicted.page_id(), evicted));
1351 }
1352 self.cache.mark_dirty(page_id);
1353 }
1354
1355 const FLUSH_BATCH_PAGES: usize = 256;
1359 for batch in dirty_pages.chunks(FLUSH_BATCH_PAGES) {
1360 self.write_dirty_page_batch(batch)?;
1361 }
1362 drop(dirty_pages);
1363 let mut page_ids = self.cache.dirty_page_ids(usize::MAX);
1367 page_ids.sort_unstable();
1368 for ids in page_ids.chunks(FLUSH_BATCH_PAGES) {
1369 let pages = self.cache.flush_dirty_pages(ids);
1370 self.write_dirty_page_batch(&pages)?;
1371 }
1372
1373 if *self.header_dirty_lock()? {
1375 self.write_header()?;
1376 }
1377
1378 Ok(())
1379 }
1380
1381 fn write_dirty_page_batch(&self, pages: &[(u32, Page)]) -> Result<(), PagerError> {
1382 if pages.is_empty() {
1383 return Ok(());
1384 }
1385 let result = (|| {
1389 let max_lsn = pages
1390 .iter()
1391 .filter_map(|(_, page)| page.header().ok().map(|header| header.lsn))
1392 .max()
1393 .unwrap_or(0);
1394 if max_lsn > 0 {
1395 self.wal.flush_until(max_lsn)?;
1396 }
1397 self.write_pages_through_dwb(pages)
1398 })();
1399 if result.is_err() {
1400 self.writeback_failed
1401 .store(true, std::sync::atomic::Ordering::Release);
1402 for (page_id, _) in pages {
1403 self.cache.mark_dirty(*page_id);
1404 }
1405 }
1406 result
1407 }
1408
1409 pub fn sync(&self) -> Result<(), PagerError> {
1411 let _ownership = self.lock_ownership_mutations();
1412 self.flush()?;
1413
1414 if self
1415 .allocation_reused
1416 .swap(false, std::sync::atomic::Ordering::AcqRel)
1417 {
1418 if let Err(error) = self.write_header_and_sync() {
1422 self.allocation_reused
1423 .store(true, std::sync::atomic::Ordering::Release);
1424 return Err(error);
1425 }
1426 }
1427
1428 let file = self.file_lock()?;
1429 file.sync_all()?;
1430
1431 Ok(())
1432 }
1433
1434 pub fn cache_stats(&self) -> super::page_cache::CacheStats {
1436 self.cache.stats()
1437 }
1438
1439 pub fn cache_len(&self) -> usize {
1442 self.cache.len()
1443 }
1444
1445 pub fn cache_capacity(&self) -> usize {
1448 self.cache.capacity()
1449 }
1450
1451 pub fn dirty_page_count(&self) -> usize {
1453 self.cache.dirty_count()
1454 }
1455
1456 pub fn dirty_fraction(&self) -> f64 {
1460 let capacity = self.cache.capacity().max(1) as f64;
1461 self.cache.dirty_count() as f64 / capacity
1462 }
1463
1464 pub fn flush_some_dirty(&self, max: usize) -> Result<usize, PagerError> {
1468 let _ownership = self.lock_ownership_mutations();
1469 self.ensure_writeback_available()?;
1470 if self.config.read_only || max == 0 {
1471 return Ok(0);
1472 }
1473 let dirty_pages = self.cache.flush_some_dirty(max);
1474 if dirty_pages.is_empty() {
1475 return Ok(0);
1476 }
1477 let count = dirty_pages.len();
1478 for (page_id, page) in dirty_pages {
1485 self.write_page(page_id, page)?;
1486 }
1487 Ok(count)
1488 }
1489
1490 pub fn path(&self) -> &Path {
1492 &self.path
1493 }
1494
1495 pub fn is_read_only(&self) -> bool {
1497 self.config.read_only
1498 }
1499
1500 pub fn file_size(&self) -> Result<u64, PagerError> {
1502 let file = self.file_lock()?;
1503 Ok(file.file_len()?)
1504 }
1505
1506 fn clear_dwb_zone(&self) -> Result<(), PagerError> {
1508 let mut file = self.file_lock()?;
1509 Ok(pager_io::clear_dwb_zone(&mut *file)?)
1510 }
1511
1512 pub fn read_manifest_page(&self) -> Result<Page, PagerError> {
1517 self.read_page(1)
1518 .map_err(|_| PagerError::ManifestZoneCorrupt(self.path.clone()))
1519 }
1520
1521 fn write_pages_through_dwb(&self, pages: &[(u32, Page)]) -> Result<(), PagerError> {
1528 if self.config.read_only {
1529 return Err(PagerError::ReadOnly);
1530 }
1531 let mut file = self.file_lock()?;
1534 self.ensure_writeback_available()?;
1535 let result = pager_io::write_pages(&mut *file, pages, self.dwb_enabled);
1536 if result.is_err() {
1537 self.writeback_failed
1540 .store(true, std::sync::atomic::Ordering::Release);
1541 }
1542 Ok(result?)
1543 }
1544
1545 fn recover_from_dwb(&self) -> Result<(), PagerError> {
1550 let mut file = self.file_lock()?;
1551 Ok(pager_io::recover_from_dwb(&mut *file)?)
1552 }
1553
1554 fn complete_allocation_fence(&self) -> Result<(), PagerError> {
1558 if self
1559 .allocation_fence_pending
1560 .load(std::sync::atomic::Ordering::Acquire)
1561 {
1562 self.write_header_and_sync()?;
1563 self.write_header_and_sync()?;
1564 self.allocation_fence_pending
1565 .store(false, std::sync::atomic::Ordering::Release);
1566 }
1567 Ok(())
1568 }
1569
1570 pub fn write_header_and_sync(&self) -> Result<(), PagerError> {
1572 let _ownership = self.lock_ownership_mutations();
1573 self.write_header()?;
1574 let file = self.file_lock()?;
1575 file.sync_all()?;
1576 Ok(())
1577 }
1578
1579 pub fn set_checkpoint_in_progress(
1581 &self,
1582 in_progress: bool,
1583 target_lsn: u64,
1584 ) -> Result<(), PagerError> {
1585 let _ownership = self.lock_ownership_mutations();
1586 let mut header = self.header_write()?;
1587 header.checkpoint_in_progress = in_progress;
1588 header.checkpoint_target_lsn = target_lsn;
1589 *self.header_dirty_lock()? = true;
1590 drop(header);
1591 self.write_header_and_sync()
1592 }
1593
1594 pub fn complete_checkpoint(&self, lsn: u64) -> Result<(), PagerError> {
1596 let _ownership = self.lock_ownership_mutations();
1597 let mut header = self.header_write()?;
1598 header.checkpoint_lsn = lsn;
1599 header.checkpoint_in_progress = false;
1600 header.checkpoint_target_lsn = 0;
1601 *self.header_dirty_lock()? = true;
1602 drop(header);
1603 self.write_header_and_sync()
1604 }
1605}