1#[krabka_macros::sendfile_cfg]
25use std::sync::atomic::AtomicBool;
26use {
27 super::{Segment, direct::TailCache},
28 crate::{error::LogError, index::OffsetIndexEntry, io::LogIo},
29 bytes::Bytes,
30 krabka_ids::Offset,
31 std::{fs::File, sync::Arc},
32};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct IndexHint {
38 pub(super) floor: Option<OffsetIndexEntry>,
40 pub(super) bound: Option<u64>,
43}
44
45pub(super) fn relative_target(
50 base: Offset,
51 target: Offset,
52 operation: &str,
53) -> Result<u32, LogError> {
54 u32::try_from((target.0 - base.0).max(0))
55 .map_err(|_| LogError::Corrupt(format!("{operation} target offset out of range")))
56}
57
58pub trait ReadSource {
65 fn file(&self) -> &Arc<File>;
67 fn io(&self) -> &Arc<dyn LogIo>;
69 fn log_size(&self) -> u64;
71 fn base(&self) -> Offset;
73 fn last(&self) -> Offset;
75 #[krabka_macros::sendfile_cfg]
79 fn wrote_direct(&self) -> bool;
80 fn tail_cache(&self) -> Option<&TailCache>;
82 #[krabka_macros::sendfile_cfg]
85 fn cache_probe_unsupported(&self) -> &AtomicBool;
86 fn index_hint(&self, target_rel: u32) -> IndexHint;
88
89 fn cached_bytes(&self, pos: u64, max: usize) -> Option<Bytes> {
92 self.tail_cache()?.bytes_from(pos, max)
93 }
94}
95
96macro_rules! field_accessors {
99 () => {
100 fn file(&self) -> &Arc<File> { &self.log_file }
101
102 fn io(&self) -> &Arc<dyn LogIo> { &self.io }
103
104 fn log_size(&self) -> u64 { self.log_size }
105
106 fn base(&self) -> Offset { self.base_offset }
107
108 fn last(&self) -> Offset { self.last_offset }
109
110 #[krabka_macros::sendfile_cfg]
111 fn wrote_direct(&self) -> bool { self.wrote_direct }
112 };
113}
114
115impl ReadSource for Segment {
116 field_accessors!();
117
118 fn tail_cache(&self) -> Option<&TailCache> { self.direct.as_ref().map(|mode| &mode.cache) }
119
120 #[krabka_macros::sendfile_cfg]
121 fn cache_probe_unsupported(&self) -> &AtomicBool { &self.cache_probe_unsupported }
122
123 fn index_hint(&self, target_rel: u32) -> IndexHint {
124 IndexHint {
125 floor: self.offset_index.floor_entry(target_rel),
126 bound: self
127 .offset_index
128 .position_at_or_after(target_rel)
129 .map(u64::from),
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
137pub struct SegmentReadView {
138 log_file: Arc<File>,
139 io: Arc<dyn LogIo>,
140 log_size: u64,
141 base_offset: Offset,
142 last_offset: Offset,
143 #[cfg(any(
144 target_os = "linux",
145 target_os = "macos",
146 target_os = "ios",
147 target_os = "tvos",
148 target_os = "watchos",
149 target_os = "freebsd",
150 target_os = "dragonfly",
151 ))]
152 wrote_direct: bool,
153 tail: Option<TailCache>,
154 #[cfg(any(
155 target_os = "linux",
156 target_os = "macos",
157 target_os = "ios",
158 target_os = "tvos",
159 target_os = "watchos",
160 target_os = "freebsd",
161 target_os = "dragonfly",
162 ))]
163 cache_probe_unsupported: Arc<AtomicBool>,
164 target_rel: u32,
166 hint: IndexHint,
167 cold_before: Option<u64>,
171}
172
173impl SegmentReadView {
174 pub fn cold_before(&self) -> Option<u64> { self.cold_before }
177
178 pub fn starts_cold(&self) -> bool {
183 self.cold_before
184 .is_some_and(|cold| self.floor_position() < cold)
185 }
186
187 fn floor_position(&self) -> u64 { self.hint.floor.map_or(0, |entry| u64::from(entry.position)) }
189
190 pub fn bytes_from_floor(&self) -> u64 { self.log_size.saturating_sub(self.floor_position()) }
194
195 #[cfg(not(target_os = "wasi"))]
200 pub fn drop_cached(&self, offset: u64, len: u64) {
201 self.io.drop_cached(&self.log_file, offset, len);
202 }
203}
204
205impl ReadSource for SegmentReadView {
206 field_accessors!();
207
208 fn tail_cache(&self) -> Option<&TailCache> { self.tail.as_ref() }
209
210 #[krabka_macros::sendfile_cfg]
211 fn cache_probe_unsupported(&self) -> &AtomicBool { &self.cache_probe_unsupported }
212
213 fn index_hint(&self, target_rel: u32) -> IndexHint {
214 assert2::debug_assert!(
215 target_rel == self.target_rel,
216 "a read view answers for the offset it was taken for"
217 );
218 self.hint
219 }
220}
221
222impl Segment {
223 pub fn read_view(
232 &self,
233 fetch_offset: Offset,
234 cold_before: Option<u64>,
235 ) -> Result<SegmentReadView, LogError> {
236 let target_rel = relative_target(self.base_offset, fetch_offset, "read_view")?;
237 Ok(SegmentReadView {
238 log_file: Arc::clone(&self.log_file),
239 io: Arc::clone(&self.io),
240 log_size: self.log_size,
241 base_offset: self.base_offset,
242 last_offset: self.last_offset,
243 #[cfg(any(
244 target_os = "linux",
245 target_os = "macos",
246 target_os = "ios",
247 target_os = "tvos",
248 target_os = "watchos",
249 target_os = "freebsd",
250 target_os = "dragonfly",
251 ))]
252 wrote_direct: self.wrote_direct,
253 tail: self.direct.as_ref().map(|mode| mode.cache.clone()),
254 #[cfg(any(
255 target_os = "linux",
256 target_os = "macos",
257 target_os = "ios",
258 target_os = "tvos",
259 target_os = "watchos",
260 target_os = "freebsd",
261 target_os = "dragonfly",
262 ))]
263 cache_probe_unsupported: Arc::clone(&self.cache_probe_unsupported),
264 target_rel,
265 hint: self.index_hint(target_rel),
266 cold_before,
267 })
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 #[krabka_macros::sendfile_cfg]
274 use crate::segment::RawDescSource as _;
275 use {
276 super::*,
277 crate::segment::{
278 RawReadSource as _,
279 test_support::{DENSE_INDEX, TempSegment, test_batch_at, test_segment},
280 },
281 krabka_units::prelude::{bytes, mebibytes},
282 };
283
284 #[krabka_macros::sendfile_cfg]
288 #[test]
289 fn a_view_reads_what_its_segment_read_when_it_was_taken() {
290 let TempSegment {
291 dir: _dir,
292 segment: mut seg,
293 } = test_segment();
294 for off in 0..12i64 {
295 seg.append(&test_batch_at(Offset(off)), DENSE_INDEX)
296 .unwrap();
297 }
298 let limit = Offset(12);
299 for fetch in 0..=12i64 {
300 for budget in [bytes(1), bytes(200), mebibytes(1)] {
301 let fetch = Offset(fetch);
302 let view = seg.read_view(fetch, None).unwrap();
303 let from_segment = seg.read_raw(fetch, limit, budget).unwrap();
304 let from_view = view
305 .read_raw_with_policy(fetch, limit, budget, mebibytes(4), mebibytes(4))
306 .unwrap();
307 assert2::check!(
308 from_view.bytes == from_segment.bytes,
309 "{fetch:?} {budget:?}"
310 );
311 assert2::check!(from_view.start_offset == from_segment.start_offset);
312 assert2::check!(from_view.last_offset == from_segment.last_offset);
313 let described = view
314 .read_raw_desc_if_cached(fetch, limit, budget, mebibytes(4))
315 .unwrap()
316 .expect("a freshly written segment is cached");
317 assert2::check!(
318 described.region.as_ref().map(|region| region.len)
319 == Some(from_segment.bytes.len()).filter(|len| *len > 0)
320 );
321 }
322 }
323
324 let view = seg.read_view(Offset(0), None).unwrap();
326 let before = seg.read_raw(Offset(0), Offset(99), mebibytes(1)).unwrap();
327 seg.append(&test_batch_at(Offset(12)), DENSE_INDEX).unwrap();
328 let after = view
329 .read_raw_with_policy(
330 Offset(0),
331 Offset(99),
332 mebibytes(1),
333 mebibytes(4),
334 mebibytes(4),
335 )
336 .unwrap();
337 assert2::check!(after.bytes == before.bytes);
338 assert2::check!(after.last_offset == Offset(11));
339 }
340
341 #[test]
344 fn the_relative_target_clamps_below_and_refuses_overflow() {
345 assert2::check!(relative_target(Offset(10), Offset(3), "test").unwrap() == 0);
346 assert2::check!(relative_target(Offset(10), Offset(17), "test").unwrap() == 7);
347 assert2::check!(
348 relative_target(Offset(0), Offset(i64::from(u32::MAX) + 1), "test").is_err()
349 );
350 }
351}