1use super::record::WalRecord;
2use reddb_file::{OpenMode, StdVfs, Vfs, VfsFile};
3use std::io::{self, SeekFrom};
4use std::path::Path;
5use std::sync::Arc;
6
7pub const WAL_BUFFER_BYTES: usize = 64 * 1024;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17enum WalSyncMethod {
18 Data,
19 All,
20}
21
22pub(crate) struct WalGroupSync<F: VfsFile> {
23 target_lsn: u64,
24 sync_handle: Arc<F>,
25 method: WalSyncMethod,
26}
27
28impl<F: VfsFile> WalGroupSync<F> {
29 pub(crate) fn target_lsn(&self) -> u64 {
30 self.target_lsn
31 }
32
33 pub(crate) fn sync(&self) -> io::Result<()> {
34 match self.method {
35 WalSyncMethod::Data => self.sync_handle.sync_data(),
36 WalSyncMethod::All => self.sync_handle.sync_all(),
37 }
38 }
39}
40
41struct VfsBufWriter<F: VfsFile> {
42 file: F,
43 buffer: Vec<u8>,
44}
45
46impl<F: VfsFile> VfsBufWriter<F> {
47 fn with_capacity(capacity: usize, file: F) -> Self {
48 Self {
49 file,
50 buffer: Vec::with_capacity(capacity),
51 }
52 }
53
54 fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {
55 if self.buffer.len() + bytes.len() > self.buffer.capacity() {
56 self.flush()?;
57 }
58 if bytes.len() >= self.buffer.capacity() {
59 self.file.write_all(bytes)
60 } else {
61 self.buffer.extend_from_slice(bytes);
62 Ok(())
63 }
64 }
65
66 fn flush(&mut self) -> io::Result<()> {
67 if !self.buffer.is_empty() {
68 self.file.write_all(&self.buffer)?;
69 self.buffer.clear();
70 }
71 Ok(())
72 }
73
74 fn get_ref(&self) -> &F {
75 &self.file
76 }
77
78 fn get_mut(&mut self) -> &mut F {
79 &mut self.file
80 }
81}
82
83impl<F: VfsFile> Drop for VfsBufWriter<F> {
84 fn drop(&mut self) {
85 let _ = self.flush();
86 }
87}
88
89fn fallocate_unsupported(err: &io::Error) -> bool {
94 if err.kind() == io::ErrorKind::Unsupported {
95 return true;
96 }
97 #[cfg(target_os = "linux")]
98 {
99 matches!(
100 err.raw_os_error(),
101 Some(libc::EOPNOTSUPP) | Some(libc::ENOSYS) | Some(libc::EINVAL)
102 )
103 }
104 #[cfg(not(target_os = "linux"))]
105 {
106 false
107 }
108}
109
110pub struct WalWriter<V: Vfs = StdVfs> {
125 file: VfsBufWriter<V::File>,
126 sync_handle: Arc<V::File>,
137 current_lsn: u64,
140 durable_lsn: u64,
146 last_synced_size: u64,
151 preallocated_to: u64,
158 prealloc_supported: bool,
163 prealloc_metadata_dirty: bool,
167 #[cfg(test)]
168 last_sync_method: Option<WalSyncMethod>,
169}
170
171impl WalWriter<StdVfs> {
172 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
174 Self::open_with_vfs(StdVfs, path)
175 }
176}
177
178impl<V: Vfs> WalWriter<V> {
179 pub fn open_with_vfs<P: AsRef<Path>>(vfs: V, path: P) -> io::Result<Self> {
181 let mut raw = vfs.open(path.as_ref(), OpenMode::create_keep())?;
185 let existing_len = raw.file_len()?;
186
187 let current_lsn = if existing_len == 0 {
188 raw.write_all(&reddb_file::encode_wal_file_header())?;
189 raw.sync_all()?;
190 reddb_file::WAL_FILE_HEADER_BYTES as u64
191 } else {
192 raw.seek(SeekFrom::End(0))?
196 };
197
198 let sync_handle = Arc::new(raw.try_clone()?);
204 let file = VfsBufWriter::with_capacity(WAL_BUFFER_BYTES, raw);
205
206 let mut writer = Self {
210 file,
211 sync_handle,
212 current_lsn,
213 durable_lsn: current_lsn,
214 last_synced_size: current_lsn,
215 preallocated_to: 0,
216 prealloc_supported: true,
217 prealloc_metadata_dirty: false,
218 #[cfg(test)]
219 last_sync_method: None,
220 };
221 writer.ensure_preallocated()?;
224 Ok(writer)
225 }
226
227 fn ensure_preallocated(&mut self) -> io::Result<()> {
238 if !self.prealloc_supported {
239 return Ok(());
240 }
241 let target = reddb_file::next_main_wal_segment_boundary(self.current_lsn);
242 if target <= self.preallocated_to {
243 return Ok(());
244 }
245 let from = self.preallocated_to;
246 match self.file.get_ref().reserve(from, target - from) {
247 Ok(()) => {
248 self.preallocated_to = target;
249 self.prealloc_metadata_dirty = true;
250 }
251 Err(ref e) if fallocate_unsupported(e) => self.prealloc_supported = false,
252 Err(_) => {
253 }
256 }
257 Ok(())
258 }
259
260 pub fn append(&mut self, record: &WalRecord) -> io::Result<u64> {
269 let bytes = record.encode();
270 self.file.write_all(&bytes)?;
271
272 let record_lsn = self.current_lsn;
273 self.current_lsn += bytes.len() as u64;
274
275 self.ensure_preallocated()?;
276 Ok(record_lsn)
277 }
278
279 pub fn append_bytes(&mut self, bytes: &[u8]) -> io::Result<u64> {
291 self.file.write_all(bytes)?;
292 let record_lsn = self.current_lsn;
293 self.current_lsn += bytes.len() as u64;
294 self.ensure_preallocated()?;
295 Ok(record_lsn)
296 }
297
298 pub fn set_current_lsn(&mut self, lsn: u64) {
305 self.current_lsn = lsn;
306 }
307
308 pub fn sync(&mut self) -> io::Result<()> {
316 self.file.flush()?;
317 self.sync_flushed_file()?;
318 self.durable_lsn = self.current_lsn;
319 Ok(())
320 }
321
322 pub fn flush_until(&mut self, target: u64) -> io::Result<()> {
330 if self.durable_lsn >= target {
331 return Ok(());
332 }
333 self.file.flush()?;
334 self.sync_flushed_file()?;
335 self.durable_lsn = self.current_lsn;
336 Ok(())
337 }
338
339 fn sync_flushed_file(&mut self) -> io::Result<()> {
340 let method = self.next_sync_method();
341 match method {
342 WalSyncMethod::Data => self.file.get_ref().sync_data()?,
343 WalSyncMethod::All => self.file.get_ref().sync_all()?,
344 }
345 self.mark_sync_complete(method, self.current_lsn);
346 Ok(())
347 }
348
349 fn next_sync_method(&self) -> WalSyncMethod {
350 if !self.prealloc_metadata_dirty && self.current_lsn <= self.last_synced_size {
351 WalSyncMethod::Data
352 } else {
353 WalSyncMethod::All
354 }
355 }
356
357 fn mark_sync_complete(&mut self, method: WalSyncMethod, lsn: u64) {
358 match method {
359 WalSyncMethod::Data => {}
360 WalSyncMethod::All => {
361 self.last_synced_size = self.preallocated_to.max(lsn);
362 self.prealloc_metadata_dirty = false;
363 }
364 }
365 #[cfg(test)]
366 {
367 self.last_sync_method = Some(method);
368 }
369 }
370
371 pub fn durable_lsn(&self) -> u64 {
375 self.durable_lsn
376 }
377
378 pub fn current_lsn(&self) -> u64 {
380 self.current_lsn
381 }
382
383 pub(crate) fn drain_for_group_sync(&mut self) -> io::Result<WalGroupSync<V::File>> {
403 self.file.flush()?;
405 Ok(WalGroupSync {
406 target_lsn: self.current_lsn,
407 sync_handle: Arc::clone(&self.sync_handle),
408 method: self.next_sync_method(),
409 })
410 }
411
412 pub(crate) fn mark_durable(&mut self, sync: &WalGroupSync<V::File>) {
418 let lsn = sync.target_lsn;
419 if lsn > self.durable_lsn {
420 self.durable_lsn = lsn;
421 }
422 self.mark_sync_complete(sync.method, lsn);
423 }
424
425 pub fn truncate(&mut self) -> io::Result<()> {
433 self.file.flush()?;
437
438 {
439 let raw = self.file.get_mut();
440 raw.set_len(0)?;
441 raw.seek(SeekFrom::Start(0))?;
442 }
443
444 self.file.write_all(&reddb_file::encode_wal_file_header())?;
446 self.file.flush()?;
447 self.file.get_ref().sync_all()?;
448
449 self.current_lsn = reddb_file::WAL_FILE_HEADER_BYTES as u64;
450 self.durable_lsn = reddb_file::WAL_FILE_HEADER_BYTES as u64;
451 self.last_synced_size = reddb_file::WAL_FILE_HEADER_BYTES as u64;
452 self.prealloc_metadata_dirty = false;
453 #[cfg(test)]
454 {
455 self.last_sync_method = Some(WalSyncMethod::All);
456 }
457
458 self.preallocated_to = 0;
462 self.ensure_preallocated()?;
463 Ok(())
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use std::path::PathBuf;
471
472 struct FileGuard {
473 path: PathBuf,
474 }
475
476 impl Drop for FileGuard {
477 fn drop(&mut self) {
478 let _ = std::fs::remove_file(&self.path);
479 }
480 }
481
482 fn temp_wal(name: &str) -> (FileGuard, PathBuf) {
483 let path = reddb_file::layout::wal_component_temp_path(
484 &std::env::temp_dir(),
485 "writer",
486 name,
487 std::process::id(),
488 );
489 let guard = FileGuard { path: path.clone() };
490 let _ = std::fs::remove_file(&path);
491 (guard, path)
492 }
493
494 #[test]
495 fn test_create_new_wal() {
496 let (_guard, path) = temp_wal("create");
497 let writer = WalWriter::open(&path).expect("open() should succeed");
498
499 assert_eq!(writer.current_lsn(), 8);
501 assert!(path.exists());
502 }
503
504 #[test]
505 fn test_append_record() {
506 let (_guard, path) = temp_wal("append");
507 let mut writer = WalWriter::open(&path).expect("open() should succeed");
508
509 let record = WalRecord::Begin { tx_id: 42 };
510 let lsn = writer.append(&record).expect("append() should succeed");
511
512 assert_eq!(lsn, 8);
514
515 assert_eq!(writer.current_lsn(), 8 + 29);
519 }
520
521 #[test]
522 fn test_append_multiple_records() {
523 let (_guard, path) = temp_wal("multi");
524 let mut writer = WalWriter::open(&path).expect("open() should succeed");
525
526 let lsn1 = writer
527 .append(&WalRecord::Begin { tx_id: 1 })
528 .expect("append() should succeed");
529 let lsn2 = writer
530 .append(&WalRecord::Begin { tx_id: 2 })
531 .expect("append() should succeed");
532 let lsn3 = writer
533 .append(&WalRecord::Commit { tx_id: 1 })
534 .expect("append() should succeed");
535
536 assert_eq!(lsn1, 8);
537 assert_eq!(lsn2, 8 + 29);
538 assert_eq!(lsn3, 8 + 29 + 29);
539 }
540
541 #[test]
542 fn test_page_write_lsn() {
543 let (_guard, path) = temp_wal("pagewrite");
544 let mut writer = WalWriter::open(&path).expect("open() should succeed");
545
546 let lsn1 = writer
548 .append(&WalRecord::Begin { tx_id: 1 })
549 .expect("append() should succeed");
550 assert_eq!(lsn1, 8);
551
552 let data = vec![1, 2, 3, 4, 5];
554 let lsn2 = writer
555 .append(&WalRecord::PageWrite {
556 tx_id: 1,
557 page_id: 100,
558 data: data.clone(),
559 })
560 .expect("value is present");
561
562 assert_eq!(lsn2, 8 + 29); assert_eq!(writer.current_lsn(), 8 + 29 + 42);
566 }
567
568 #[test]
569 fn test_sync() {
570 let (_guard, path) = temp_wal("sync");
571 let mut writer = WalWriter::open(&path).expect("open() should succeed");
572
573 writer
574 .append(&WalRecord::Begin { tx_id: 1 })
575 .expect("append() should succeed");
576 writer.sync().expect("sync() should succeed");
577
578 assert!(path.exists());
580 }
581
582 #[test]
583 fn test_truncate() {
584 let (_guard, path) = temp_wal("truncate");
585 let mut writer = WalWriter::open(&path).expect("open() should succeed");
586
587 writer
589 .append(&WalRecord::Begin { tx_id: 1 })
590 .expect("append() should succeed");
591 writer
592 .append(&WalRecord::PageWrite {
593 tx_id: 1,
594 page_id: 0,
595 data: vec![0; 100],
596 })
597 .expect("value is present");
598 writer
599 .append(&WalRecord::Commit { tx_id: 1 })
600 .expect("append() should succeed");
601
602 let lsn_before = writer.current_lsn();
603 assert!(lsn_before > 8);
604
605 writer.truncate().expect("truncate() should succeed");
607
608 assert_eq!(writer.current_lsn(), 8);
610
611 let len = std::fs::metadata(&path)
613 .expect("metadata() should succeed")
614 .len();
615 assert_eq!(len, 8);
616 }
617
618 #[test]
619 fn test_reopen_existing() {
620 let (_guard, path) = temp_wal("reopen");
621
622 let lsn_after_write;
624 {
625 let mut writer = WalWriter::open(&path).expect("open() should succeed");
626 writer
627 .append(&WalRecord::Begin { tx_id: 1 })
628 .expect("append() should succeed");
629 writer
630 .append(&WalRecord::Commit { tx_id: 1 })
631 .expect("append() should succeed");
632 lsn_after_write = writer.current_lsn();
633 }
634
635 {
637 let writer = WalWriter::open(&path).expect("open() should succeed");
638 assert_eq!(writer.current_lsn(), lsn_after_write);
640 }
641 }
642
643 #[test]
644 fn test_checkpoint_record() {
645 let (_guard, path) = temp_wal("checkpoint");
646 let mut writer = WalWriter::open(&path).expect("open() should succeed");
647
648 let lsn = writer
650 .append(&WalRecord::Checkpoint { lsn: 12345 })
651 .expect("value is present");
652 assert_eq!(lsn, 8);
653 assert_eq!(writer.current_lsn(), 8 + 29);
654 }
655
656 #[test]
661 fn fresh_wal_has_durable_lsn_at_header_end() {
662 let (_guard, path) = temp_wal("durable_init");
663 let writer = WalWriter::open(&path).expect("open() should succeed");
664 assert_eq!(writer.durable_lsn(), 8);
665 assert_eq!(writer.current_lsn(), 8);
666 }
667
668 #[test]
669 fn flush_until_below_durable_is_noop() {
670 let (_guard, path) = temp_wal("flush_noop");
671 let mut writer = WalWriter::open(&path).expect("open() should succeed");
672 let before = writer.durable_lsn();
674 writer.flush_until(0).expect("flush_until() should succeed");
675 writer.flush_until(8).expect("flush_until() should succeed");
676 assert_eq!(writer.durable_lsn(), before);
677 }
678
679 #[test]
680 fn flush_until_advances_durable_to_current() {
681 let (_guard, path) = temp_wal("flush_advance");
682 let mut writer = WalWriter::open(&path).expect("open() should succeed");
683 writer
684 .append(&WalRecord::Begin { tx_id: 7 })
685 .expect("append() should succeed");
686 writer
687 .append(&WalRecord::Commit { tx_id: 7 })
688 .expect("append() should succeed");
689 let target = writer.current_lsn();
690 assert_eq!(writer.durable_lsn(), 8);
692 writer
693 .flush_until(target)
694 .expect("flush_until() should succeed");
695 assert_eq!(writer.durable_lsn(), target);
696 }
697
698 #[test]
699 fn flush_until_is_monotonic() {
700 let (_guard, path) = temp_wal("flush_monotonic");
701 let mut writer = WalWriter::open(&path).expect("open() should succeed");
702 writer
703 .append(&WalRecord::Begin { tx_id: 1 })
704 .expect("append() should succeed");
705 let lo = writer.current_lsn();
706 writer
707 .flush_until(lo)
708 .expect("flush_until() should succeed");
709 let durable_after_lo = writer.durable_lsn();
710 writer
711 .append(&WalRecord::Commit { tx_id: 1 })
712 .expect("append() should succeed");
713 let hi = writer.current_lsn();
714 writer
715 .flush_until(hi)
716 .expect("flush_until() should succeed");
717 assert!(writer.durable_lsn() >= durable_after_lo);
718 writer
720 .flush_until(lo)
721 .expect("flush_until() should succeed");
722 assert_eq!(writer.durable_lsn(), hi);
723 }
724
725 #[test]
726 fn sync_advances_durable_lsn_too() {
727 let (_guard, path) = temp_wal("sync_durable");
728 let mut writer = WalWriter::open(&path).expect("open() should succeed");
729 writer
730 .append(&WalRecord::Begin { tx_id: 9 })
731 .expect("append() should succeed");
732 let before = writer.durable_lsn();
733 let after_append = writer.current_lsn();
734 assert!(after_append > before);
735 writer.sync().expect("sync() should succeed");
736 assert_eq!(writer.durable_lsn(), after_append);
737 }
738
739 #[test]
740 fn sync_all_is_used_when_wal_size_grew() {
741 let (_guard, path) = temp_wal("sync_all_grew");
742 let mut writer = WalWriter::open(&path).expect("open() should succeed");
743
744 writer
745 .append(&WalRecord::Begin { tx_id: 1 })
746 .expect("append() should succeed");
747 writer.sync().expect("sync() should succeed");
748
749 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::All));
750 assert!(writer.last_synced_size >= writer.current_lsn());
751 assert!(!writer.prealloc_metadata_dirty);
752 }
753
754 #[test]
755 fn sync_all_is_used_for_metadata_only_preallocation() {
756 let (_guard, path) = temp_wal("sync_all_prealloc_metadata");
757 let mut writer = WalWriter::open(&path).expect("open() should succeed");
758 if !writer.prealloc_supported {
759 return;
760 }
761
762 assert_eq!(writer.current_lsn(), 8);
763 assert!(writer.prealloc_metadata_dirty);
764
765 writer.sync().expect("sync() should succeed");
766
767 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::All));
768 assert_eq!(writer.last_synced_size, writer.preallocated_to);
769 assert!(!writer.prealloc_metadata_dirty);
770 }
771
772 #[test]
773 fn sync_data_is_used_when_wal_size_is_unchanged() {
774 let (_guard, path) = temp_wal("sync_data_unchanged");
775 let mut writer = WalWriter::open(&path).expect("open() should succeed");
776
777 writer
778 .append(&WalRecord::Begin { tx_id: 1 })
779 .expect("append() should succeed");
780 writer.sync().expect("sync() should succeed");
781 let synced_size = writer.last_synced_size;
782 writer.sync().expect("sync() should succeed");
783
784 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::Data));
785 assert_eq!(writer.last_synced_size, synced_size);
786 assert_eq!(writer.durable_lsn(), writer.current_lsn());
787 }
788
789 #[test]
790 fn sync_data_is_used_for_appends_within_synced_preallocation() {
791 let (_guard, path) = temp_wal("sync_data_preallocated_append");
792 let mut writer = WalWriter::open(&path).expect("open() should succeed");
793 if !writer.prealloc_supported {
794 return;
795 }
796
797 writer
798 .append(&WalRecord::Begin { tx_id: 1 })
799 .expect("append() should succeed");
800 writer.sync().expect("sync() should succeed");
801 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::All));
802
803 writer
804 .append(&WalRecord::Commit { tx_id: 1 })
805 .expect("append() should succeed");
806 writer.sync().expect("sync() should succeed");
807
808 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::Data));
809 assert_eq!(writer.durable_lsn(), writer.current_lsn());
810 assert!(writer.current_lsn() <= writer.last_synced_size);
811 }
812
813 #[test]
814 fn group_sync_uses_sync_data_within_synced_preallocation() {
815 let (_guard, path) = temp_wal("group_sync_data_preallocated_append");
816 let mut writer = WalWriter::open(&path).expect("open() should succeed");
817 if !writer.prealloc_supported {
818 return;
819 }
820
821 writer
822 .append(&WalRecord::Begin { tx_id: 1 })
823 .expect("append() should succeed");
824 writer.sync().expect("sync() should succeed");
825 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::All));
826
827 writer
828 .append(&WalRecord::Commit { tx_id: 1 })
829 .expect("append() should succeed");
830 let sync = writer
831 .drain_for_group_sync()
832 .expect("drain_for_group_sync() should succeed");
833 assert_eq!(sync.method, WalSyncMethod::Data);
834 sync.sync().expect("sync() should succeed");
835 writer.mark_durable(&sync);
836
837 assert_eq!(writer.last_sync_method, Some(WalSyncMethod::Data));
838 assert_eq!(writer.durable_lsn(), writer.current_lsn());
839 }
840
841 #[test]
842 fn truncate_resets_durable_lsn() {
843 let (_guard, path) = temp_wal("truncate_durable");
844 let mut writer = WalWriter::open(&path).expect("open() should succeed");
845 writer
846 .append(&WalRecord::Begin { tx_id: 1 })
847 .expect("append() should succeed");
848 writer.sync().expect("sync() should succeed");
849 assert!(writer.durable_lsn() > 8);
850 writer.truncate().expect("truncate() should succeed");
851 assert_eq!(writer.durable_lsn(), 8);
852 assert_eq!(writer.current_lsn(), 8);
853 }
854
855 #[test]
856 fn reopen_initialises_durable_to_current() {
857 let (_guard, path) = temp_wal("reopen_durable");
858 {
859 let mut writer = WalWriter::open(&path).expect("open() should succeed");
860 writer
861 .append(&WalRecord::Begin { tx_id: 1 })
862 .expect("append() should succeed");
863 writer.sync().expect("sync() should succeed");
864 }
865 let writer = WalWriter::open(&path).expect("open() should succeed");
866 assert_eq!(writer.durable_lsn(), writer.current_lsn());
868 }
869
870 #[test]
875 fn bufwriter_coalesces_until_sync() {
876 let (_guard, path) = temp_wal("bufwriter_coalesce");
880 let mut writer = WalWriter::open(&path).expect("open() should succeed");
881 for tx in 0..100u64 {
882 writer
883 .append(&WalRecord::Begin { tx_id: tx })
884 .expect("append() should succeed");
885 }
886 assert_eq!(writer.current_lsn(), 8 + 100 * 29);
890 let on_disk = std::fs::metadata(&path)
892 .expect("metadata() should succeed")
893 .len();
894 assert_eq!(on_disk, 8, "BufWriter leaked bytes to disk before sync");
895 }
896
897 #[test]
898 fn sync_drains_bufwriter_before_fsync() {
899 let (_guard, path) = temp_wal("sync_drains");
902 let mut writer = WalWriter::open(&path).expect("open() should succeed");
903 for tx in 0..50u64 {
904 writer
905 .append(&WalRecord::Begin { tx_id: tx })
906 .expect("append() should succeed");
907 }
908 writer.sync().expect("sync() should succeed");
909 let on_disk = std::fs::metadata(&path)
910 .expect("metadata() should succeed")
911 .len();
912 assert_eq!(on_disk, writer.current_lsn());
913 assert_eq!(writer.durable_lsn(), writer.current_lsn());
914 }
915
916 #[test]
917 fn flush_until_drains_bufwriter_too() {
918 let (_guard, path) = temp_wal("flush_until_drains");
922 let mut writer = WalWriter::open(&path).expect("open() should succeed");
923 for tx in 0..30u64 {
924 writer
925 .append(&WalRecord::Begin { tx_id: tx })
926 .expect("append() should succeed");
927 }
928 let target = writer.current_lsn();
929 writer
930 .flush_until(target)
931 .expect("flush_until() should succeed");
932 let on_disk = std::fs::metadata(&path)
933 .expect("metadata() should succeed")
934 .len();
935 assert_eq!(on_disk, target);
936 assert_eq!(writer.durable_lsn(), target);
937 }
938
939 #[test]
940 fn truncate_drains_pending_bufwriter_bytes_first() {
941 let (_guard, path) = temp_wal("truncate_drain");
946 let mut writer = WalWriter::open(&path).expect("open() should succeed");
947 for tx in 0..200u64 {
950 writer
951 .append(&WalRecord::Begin { tx_id: tx })
952 .expect("append() should succeed");
953 }
954 assert_eq!(
956 std::fs::metadata(&path)
957 .expect("metadata() should succeed")
958 .len(),
959 8
960 );
961
962 writer.truncate().expect("truncate() should succeed");
963 let on_disk = std::fs::metadata(&path)
965 .expect("metadata() should succeed")
966 .len();
967 assert_eq!(on_disk, 8);
968 assert_eq!(writer.current_lsn(), 8);
969 assert_eq!(writer.durable_lsn(), 8);
970
971 writer
973 .append(&WalRecord::Begin { tx_id: 99 })
974 .expect("append() should succeed");
975 writer.sync().expect("sync() should succeed");
976 assert_eq!(
977 std::fs::metadata(&path)
978 .expect("metadata() should succeed")
979 .len(),
980 8 + 29
981 );
982 }
983
984 #[test]
985 fn reopen_sees_only_synced_records() {
986 let (_guard, path) = temp_wal("reopen_synced_only");
995 let synced_lsn;
996 {
997 let mut writer = WalWriter::open(&path).expect("open() should succeed");
998 writer
999 .append(&WalRecord::Begin { tx_id: 1 })
1000 .expect("append() should succeed");
1001 writer.sync().expect("sync() should succeed");
1002 synced_lsn = writer.current_lsn();
1003 for tx in 100..120u64 {
1006 writer
1007 .append(&WalRecord::Begin { tx_id: tx })
1008 .expect("append() should succeed");
1009 }
1010 }
1016 let writer = WalWriter::open(&path).expect("open() should succeed");
1017 assert!(writer.durable_lsn() >= synced_lsn);
1022 }
1023
1024 fn allocated_bytes(path: &std::path::Path) -> u64 {
1031 use fs2::FileExt;
1032 let f = std::fs::File::open(path).expect("open() should succeed");
1033 f.allocated_size().expect("allocated_size() should succeed")
1034 }
1035
1036 #[test]
1037 fn segment_boundary_rounds_strictly_up() {
1038 assert_eq!(
1041 reddb_file::next_main_wal_segment_boundary(0),
1042 reddb_file::MAIN_WAL_SEGMENT_BYTES
1043 );
1044 assert_eq!(
1045 reddb_file::next_main_wal_segment_boundary(8),
1046 reddb_file::MAIN_WAL_SEGMENT_BYTES
1047 );
1048 assert_eq!(
1049 reddb_file::next_main_wal_segment_boundary(reddb_file::MAIN_WAL_SEGMENT_BYTES - 1),
1050 reddb_file::MAIN_WAL_SEGMENT_BYTES
1051 );
1052 assert_eq!(
1054 reddb_file::next_main_wal_segment_boundary(reddb_file::MAIN_WAL_SEGMENT_BYTES),
1055 2 * reddb_file::MAIN_WAL_SEGMENT_BYTES
1056 );
1057 assert_eq!(
1058 reddb_file::next_main_wal_segment_boundary(reddb_file::MAIN_WAL_SEGMENT_BYTES + 1),
1059 2 * reddb_file::MAIN_WAL_SEGMENT_BYTES
1060 );
1061 }
1062
1063 #[test]
1064 fn open_preallocates_first_segment() {
1065 let (_guard, path) = temp_wal("prealloc_open");
1068 let writer = WalWriter::open(&path).expect("open() should succeed");
1069 if !writer.prealloc_supported {
1070 return; }
1072 assert_eq!(writer.preallocated_to, reddb_file::MAIN_WAL_SEGMENT_BYTES);
1073 assert!(allocated_bytes(&path) >= reddb_file::MAIN_WAL_SEGMENT_BYTES);
1076 assert_eq!(
1077 std::fs::metadata(&path)
1078 .expect("metadata() should succeed")
1079 .len(),
1080 8
1081 );
1082 }
1083
1084 #[test]
1085 fn preallocation_does_not_grow_logical_length() {
1086 let (_guard, path) = temp_wal("prealloc_logical");
1092 let mut writer = WalWriter::open(&path).expect("open() should succeed");
1093 for tx in 0..50u64 {
1094 writer
1095 .append(&WalRecord::Begin { tx_id: tx })
1096 .expect("append() should succeed");
1097 }
1098 writer.sync().expect("sync() should succeed");
1099 let logical = std::fs::metadata(&path)
1100 .expect("metadata() should succeed")
1101 .len();
1102 assert_eq!(logical, 8 + 50 * 29, "preallocation inflated i_size");
1103 assert_eq!(writer.current_lsn(), logical);
1104 }
1105
1106 #[test]
1107 fn truncate_re_extends_a_fresh_segment() {
1108 let (_guard, path) = temp_wal("prealloc_truncate");
1111 let mut writer = WalWriter::open(&path).expect("open() should succeed");
1112 writer
1113 .append(&WalRecord::Begin { tx_id: 1 })
1114 .expect("append() should succeed");
1115 writer.sync().expect("sync() should succeed");
1116
1117 writer.truncate().expect("truncate() should succeed");
1118
1119 assert_eq!(writer.current_lsn(), 8);
1120 assert_eq!(
1121 std::fs::metadata(&path)
1122 .expect("metadata() should succeed")
1123 .len(),
1124 8
1125 );
1126 if writer.prealloc_supported {
1127 assert_eq!(writer.preallocated_to, reddb_file::MAIN_WAL_SEGMENT_BYTES);
1128 assert!(allocated_bytes(&path) >= reddb_file::MAIN_WAL_SEGMENT_BYTES);
1129 }
1130 }
1131
1132 #[test]
1133 fn preallocated_wal_recovers_records_without_trailing_garbage() {
1134 use super::super::reader::WalReader;
1138 let (_guard, path) = temp_wal("prealloc_recover");
1139 {
1140 let mut writer = WalWriter::open(&path).expect("open() should succeed");
1141 writer
1142 .append(&WalRecord::Begin { tx_id: 1 })
1143 .expect("append() should succeed");
1144 writer
1145 .append(&WalRecord::PageWrite {
1146 tx_id: 1,
1147 page_id: 7,
1148 data: vec![1, 2, 3, 4],
1149 })
1150 .expect("value is present");
1151 writer
1152 .append(&WalRecord::Commit { tx_id: 1 })
1153 .expect("append() should succeed");
1154 writer.sync().expect("sync() should succeed");
1155 }
1156 let records: Vec<_> = WalReader::open(&path)
1157 .expect("value is present")
1158 .iter()
1159 .collect::<Result<_, _>>()
1160 .expect("reader must stop cleanly at real EOF, not in reserved tail");
1161 assert_eq!(records.len(), 3);
1162 assert_eq!(records[0].1, WalRecord::Begin { tx_id: 1 });
1163 assert_eq!(records[2].1, WalRecord::Commit { tx_id: 1 });
1164 }
1165}