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