1#[cfg(unix)]
9use crate::io::LogIo;
10#[cfg(not(target_os = "wasi"))]
12#[cfg(unix)]
13use std::path::Path;
14use {
15 super::Log,
16 crate::{error::LogError, segment::Segment},
17 krabka_ids::Offset,
18 std::time::Instant,
19};
20
21#[cfg(test)]
22pub mod sync_observer {
23 #[cfg(unix)]
24 use std::path::PathBuf;
25 use {krabka_ids::Offset, std::cell::RefCell};
26
27 #[cfg(unix)]
28 thread_local! {
29 static DIR_SYNCS: RefCell<Vec<PathBuf>> = const { RefCell::new(Vec::new()) };
30 }
31
32 thread_local! {
33 static SEGMENT_FLUSHES: RefCell<Vec<Offset>> = const { RefCell::new(Vec::new()) };
34 }
35
36 #[cfg(unix)]
37 pub(super) fn take_dir_syncs() -> Vec<PathBuf> { DIR_SYNCS.take() }
38
39 pub fn take_segment_flushes() -> Vec<Offset> { SEGMENT_FLUSHES.take() }
40
41 #[cfg(unix)]
42 pub(super) fn record_dir_sync(dir: PathBuf) {
43 DIR_SYNCS.with_borrow_mut(|synced| synced.push(dir));
44 }
45
46 pub(super) fn record_segment_flush(base: Offset) {
47 SEGMENT_FLUSHES.with_borrow_mut(|flushed| flushed.push(base));
48 }
49}
50
51impl Log {
52 pub fn sync(&mut self) -> Result<(), LogError> {
63 self.rollover_flusher.finish()?;
64 #[cfg(not(target_os = "wasi"))]
69 {
70 self.writeback_unsynced = 0;
71 self.writeback_jobs.clear();
72 if self.active.is_some() {
73 let budget = self.page_cache_budget();
74 self.hand_over_pages(budget)?;
75 }
76 }
77 for segment in &mut self.segments {
78 Self::segment_flush(segment)?;
79 }
80 self.active_segment_flush()?;
81 if self.dir_sync_needed {
82 #[cfg(unix)]
88 Self::sync_log_dir(&*self.io, &self.dir)?;
89 self.dir_sync_needed = false;
90 }
91 self.flush_log_start_checkpoint()?;
94 self.unflushed_messages = 0;
95 self.last_flush = Instant::now();
96 Ok(())
97 }
98
99 #[cfg(unix)]
100 fn sync_log_dir(io: &dyn LogIo, dir: &Path) -> Result<(), LogError> {
101 io.sync_dir(dir)?;
102 #[cfg(test)]
103 sync_observer::record_dir_sync(dir.to_path_buf());
104 Ok(())
105 }
106
107 pub(super) fn active_segment_flush(&mut self) -> Result<(), LogError> {
108 let active = self.active.as_mut().ok_or_else(super::no_active_segment)?;
109 Self::segment_flush(active)
110 }
111
112 pub(super) fn rollback_failed_append(&mut self, base_offset: Offset) -> Result<(), LogError> {
113 self.truncate_to(base_offset)?;
117 self.active_segment_flush()
118 }
119
120 fn segment_flush(segment: &mut Segment) -> Result<(), LogError> {
121 #[cfg(test)]
122 sync_observer::record_segment_flush(segment.base_offset());
123 segment.flush()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use {
130 super::*,
131 crate::{
132 log::test_support::{rolling_test_log, sample_batch},
133 test_support::open_log,
134 },
135 krabka_ids::Offset,
136 };
137
138 #[test]
139 fn sync_persists_appended_records() {
140 let dir = tempfile::tempdir().unwrap();
141 {
142 let mut log = open_log(dir.path());
143 log.append(&mut sample_batch(3)).unwrap();
144 log.sync().unwrap(); }
146 let log = open_log(dir.path());
148 assert2::assert!(log.log_end_offset() == Offset(3));
149 }
150
151 #[cfg(unix)]
152 #[yare::parameterized(
153 initial_creation = { "initial creation" },
154 reopen_before_prior_sync = { "reopen before prior sync" },
155 rollover = { "rollover" },
156 )]
157 fn sync_fsyncs_parent_dir_after_segment_lifecycle_events(case: &str) {
158 let dir = tempfile::tempdir().unwrap();
159 let mut log = match case {
160 "initial creation" => open_log(dir.path()),
161 "reopen before prior sync" => {
162 drop(open_log(dir.path()));
163 open_log(dir.path())
164 }
165 "rollover" => {
166 let mut log = rolling_test_log(dir.path());
167 log.append(&mut sample_batch(1)).unwrap();
168 log.sync().unwrap();
169 log.append(&mut sample_batch(1)).unwrap();
170 log
171 }
172 _ => unreachable!("known yare case: {case}"),
173 };
174 sync_observer::take_dir_syncs();
175
176 log.sync().unwrap();
177
178 assert2::assert!(sync_observer::take_dir_syncs() == vec![dir.path().to_path_buf()]);
179 }
180
181 #[test]
182 fn sync_flushes_sealed_and_active_segments_after_rollover() {
183 let dir = tempfile::tempdir().unwrap();
184 let mut log = rolling_test_log(dir.path());
185 log.append(&mut sample_batch(1)).unwrap();
186 log.sync().unwrap();
187 sync_observer::take_segment_flushes();
188
189 log.append(&mut sample_batch(1)).unwrap();
190 log.sync().unwrap();
191
192 assert2::assert!(sync_observer::take_segment_flushes() == vec![Offset(0), Offset(1)]);
195 }
196}