1use std::sync::atomic::{AtomicU8, Ordering};
19
20mod file;
21mod swap;
22
23pub use file::set_scratch_dir;
24pub use swap::advise_pageout;
25
26use crate::pager::file::FileInner;
27use crate::pager::swap::SwapInner;
28
29#[derive(Debug)]
32pub struct Handle {
33 inner: HandleInner,
34}
35
36#[derive(Debug)]
37enum HandleInner {
38 Swap(SwapInner),
39 File(FileInner),
40}
41
42impl Handle {
43 pub fn len(&self) -> usize {
45 match &self.inner {
46 HandleInner::Swap(s) => s.total_len(),
47 HandleInner::File(f) => f.len_u64s,
48 }
49 }
50
51 pub fn len_bytes(&self) -> usize {
53 self.len() * 8
54 }
55
56 pub fn is_empty(&self) -> bool {
58 self.len() == 0
59 }
60
61 pub(crate) fn from_swap(inner: SwapInner) -> Self {
62 Self {
63 inner: HandleInner::Swap(inner),
64 }
65 }
66
67 pub(crate) fn from_file(inner: FileInner) -> Self {
68 Self {
69 inner: HandleInner::File(inner),
70 }
71 }
72
73 pub(crate) fn swap_inner(&self) -> Option<&SwapInner> {
74 match &self.inner {
75 HandleInner::Swap(s) => Some(s),
76 HandleInner::File(_) => None,
77 }
78 }
79
80 pub(crate) fn file_inner(&self) -> Option<&FileInner> {
81 match &self.inner {
82 HandleInner::File(f) => Some(f),
83 HandleInner::Swap(_) => None,
84 }
85 }
86
87 pub(crate) fn into_swap_inner(self) -> Option<SwapInner> {
88 match self.inner {
89 HandleInner::Swap(s) => Some(s),
90 HandleInner::File(_) => None,
91 }
92 }
93
94 pub(crate) fn into_file_inner(self) -> Option<FileInner> {
95 match self.inner {
96 HandleInner::File(f) => Some(f),
97 HandleInner::Swap(_) => None,
98 }
99 }
100}
101
102#[derive(Copy, Clone, Debug, Eq, PartialEq)]
104pub enum Backend {
105 Swap,
107 File,
109}
110
111const BACKEND_SWAP: u8 = 0;
112const BACKEND_FILE: u8 = 1;
113
114static BACKEND: AtomicU8 = AtomicU8::new(BACKEND_SWAP);
115
116pub fn backend() -> Backend {
118 match BACKEND.load(Ordering::Relaxed) {
119 BACKEND_SWAP => Backend::Swap,
120 BACKEND_FILE => Backend::File,
121 _ => unreachable!("BACKEND atomic holds invalid discriminant"),
122 }
123}
124
125pub fn set_backend(b: Backend) {
127 let raw = match b {
128 Backend::Swap => BACKEND_SWAP,
129 Backend::File => BACKEND_FILE,
130 };
131 BACKEND.store(raw, Ordering::Relaxed);
132}
133
134pub fn pageout(chunks: &mut [Vec<u64>]) -> Handle {
142 pageout_with(backend(), chunks)
143}
144
145pub fn pageout_with(b: Backend, chunks: &mut [Vec<u64>]) -> Handle {
151 match b {
152 Backend::Swap => swap::pageout_swap(chunks),
153 Backend::File => file::pageout_file(chunks),
154 }
155}
156
157pub fn try_pageout(chunks: &mut [Vec<u64>]) -> std::io::Result<Handle> {
161 try_pageout_with(backend(), chunks)
162}
163
164pub fn try_pageout_with(b: Backend, chunks: &mut [Vec<u64>]) -> std::io::Result<Handle> {
166 match b {
167 Backend::Swap => Ok(swap::pageout_swap(chunks)),
168 Backend::File => file::try_pageout_file(chunks),
169 }
170}
171
172pub fn read_at_many(handle: &Handle, ranges: &[(usize, usize)], dst: &mut Vec<u64>) {
178 assert_ranges_disjoint(ranges);
179 match &handle.inner {
180 HandleInner::Swap(_) => swap::read_at_swap(handle, ranges, dst),
181 HandleInner::File(_) => file::read_at_file(handle, ranges, dst),
182 }
183}
184
185pub fn try_read_at_many(
188 handle: &Handle,
189 ranges: &[(usize, usize)],
190 dst: &mut Vec<u64>,
191) -> std::io::Result<()> {
192 assert_ranges_disjoint(ranges);
193 match &handle.inner {
194 HandleInner::Swap(_) => {
195 swap::read_at_swap(handle, ranges, dst);
196 Ok(())
197 }
198 HandleInner::File(_) => file::try_read_at_file(handle, ranges, dst),
199 }
200}
201
202fn assert_ranges_disjoint(ranges: &[(usize, usize)]) {
209 let mut sorted: Vec<(usize, usize)> = ranges.iter().copied().filter(|&(_, l)| l > 0).collect();
211 sorted.sort_by_key(|&(off, _)| off);
212 let mut prev_end: usize = 0;
213 for (off, len) in sorted {
214 assert!(
215 off >= prev_end,
216 "read_at_many: overlapping ranges (range starting at {off} overlaps a previous range ending at {prev_end})",
217 );
218 prev_end = off
219 .checked_add(len)
220 .expect("range offset+len overflow in assert_ranges_disjoint");
221 }
222}
223
224pub fn read_at(handle: &Handle, offset: usize, len: usize, dst: &mut Vec<u64>) {
226 read_at_many(handle, &[(offset, len)], dst);
227}
228
229pub fn try_read_at(
231 handle: &Handle,
232 offset: usize,
233 len: usize,
234 dst: &mut Vec<u64>,
235) -> std::io::Result<()> {
236 try_read_at_many(handle, &[(offset, len)], dst)
237}
238
239pub fn take(handle: Handle, dst: &mut Vec<u64>) {
244 match &handle.inner {
245 HandleInner::Swap(_) => swap::take_swap(handle, dst),
246 HandleInner::File(_) => file::take_file(handle, dst),
247 }
248}
249
250pub fn try_take(handle: Handle, dst: &mut Vec<u64>) -> std::io::Result<()> {
253 match &handle.inner {
254 HandleInner::Swap(_) => {
255 swap::take_swap(handle, dst);
256 Ok(())
257 }
258 HandleInner::File(_) => file::try_take_file(handle, dst),
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[mz_ore::test]
267 fn backend_round_trip() {
268 set_backend(Backend::File);
269 assert_eq!(backend(), Backend::File);
270 set_backend(Backend::Swap);
271 assert_eq!(backend(), Backend::Swap);
272 }
273
274 #[mz_ore::test]
275 fn disjoint_check_accepts_sorted_adjacent_and_unsorted_disjoint() {
276 assert_ranges_disjoint(&[(0, 4), (4, 4)]);
278 assert_ranges_disjoint(&[(0, 2), (10, 3), (100, 1)]);
280 assert_ranges_disjoint(&[(10, 3), (0, 2), (100, 1)]);
282 assert_ranges_disjoint(&[(5, 0), (5, 0), (5, 0)]);
284 }
285
286 #[mz_ore::test]
287 #[should_panic(expected = "overlapping ranges")]
288 fn disjoint_check_rejects_overlap() {
289 assert_ranges_disjoint(&[(0, 4), (2, 4)]);
290 }
291
292 #[mz_ore::test]
293 #[should_panic(expected = "overlapping ranges")]
294 fn disjoint_check_rejects_overlap_unsorted() {
295 assert_ranges_disjoint(&[(10, 5), (0, 2), (12, 1)]);
296 }
297
298 #[mz_ore::test]
299 #[should_panic(expected = "overlapping ranges")]
300 fn disjoint_check_rejects_duplicate_range() {
301 assert_ranges_disjoint(&[(3, 2), (3, 2)]);
302 }
303}
304
305#[cfg(test)]
306mod dispatch_tests {
307 use super::*;
308
309 #[mz_ore::test]
310 fn end_to_end_swap() {
311 set_backend(Backend::Swap);
312 let mut chunks = [vec![1u64, 2, 3, 4]];
313 let h = pageout(&mut chunks);
314 assert_eq!(h.len(), 4);
315 assert!(chunks[0].is_empty());
316
317 let mut dst = Vec::new();
318 read_at(&h, 1, 2, &mut dst);
319 assert_eq!(dst, vec![2, 3]);
320
321 let mut dst2 = Vec::new();
322 take(h, &mut dst2);
323 assert_eq!(dst2, vec![1, 2, 3, 4]);
324 }
325}