1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4 super::{
5 IO_PRIO_BE_HIGHEST,
6 memory::{IoBufferChunk, PageAlignedMemory},
7 },
8 crate::{FileSize, IoSize, buffered_reader::FileBufRead, io_uring::sqpoll},
9 agave_io_uring::{Completion, Ring, RingAccess as _, RingOp},
10 io_uring::{IoUring, opcode, squeue, types},
11 std::{
12 collections::VecDeque,
13 fs::{File, OpenOptions},
14 io::{self, BufRead, Read},
15 marker::PhantomData,
16 mem,
17 ops::{Deref, DerefMut},
18 os::{
19 fd::{AsRawFd, BorrowedFd, RawFd},
20 unix::fs::OpenOptionsExt,
21 },
22 path::Path,
23 slice,
24 },
25};
26
27const DEFAULT_READ_SIZE: IoSize = 1024 * 1024;
31const DEFAULT_MAX_IOWQ_WORKERS: u32 = 2;
34const DIRECT_IO_READ_LEN_ALIGNMENT: IoSize = 4096;
37
38pub struct SequentialFileReaderBuilder<'sp> {
40 read_capacity: IoSize,
41 max_iowq_workers: u32,
42 ring_squeue_size: Option<u32>,
43 shared_sqpoll_fd: Option<BorrowedFd<'sp>>,
44 register_buffer: bool,
46 use_direct_io: bool,
48}
49
50impl<'sp> SequentialFileReaderBuilder<'sp> {
51 pub fn new() -> Self {
52 Self {
53 read_capacity: DEFAULT_READ_SIZE,
54 max_iowq_workers: DEFAULT_MAX_IOWQ_WORKERS,
55 ring_squeue_size: None,
56 shared_sqpoll_fd: None,
57 register_buffer: false,
58 use_direct_io: false,
59 }
60 }
61
62 #[cfg(test)]
66 pub fn read_capacity(mut self, read_capacity: IoSize) -> Self {
67 self.read_capacity = read_capacity;
68 self
69 }
70
71 pub fn use_registered_buffers(mut self, register_buffers: bool) -> Self {
75 self.register_buffer = register_buffers;
76 self
77 }
78
79 pub fn use_direct_io(mut self, use_direct_io: bool) -> Self {
84 self.use_direct_io = use_direct_io;
85 self
86 }
87
88 pub fn shared_sqpoll(mut self, shared_sqpoll_fd: Option<BorrowedFd<'sp>>) -> Self {
90 self.shared_sqpoll_fd = shared_sqpoll_fd;
91 self
92 }
93
94 pub fn build<'a>(self, buf_capacity: usize) -> io::Result<SequentialFileReader<'a>> {
101 let buf_capacity = buf_capacity.max(self.read_capacity as usize);
102 let buffer = PageAlignedMemory::new(buf_capacity)?;
103 self.build_with_buffer(buffer)
104 }
105
106 fn build_with_buffer<'a>(
113 self,
114 mut buffer: PageAlignedMemory,
115 ) -> io::Result<SequentialFileReader<'a>> {
116 let buf_capacity =
118 buffer.as_mut().len() / self.read_capacity as usize * self.read_capacity as usize;
119 assert_ne!(buf_capacity, 0, "read size aligned buffer is too small");
120 let buf_slice_mut = &mut buffer.as_mut()[..buf_capacity];
121
122 let buffers = unsafe {
125 IoBufferChunk::split_buffer_chunks(
126 buf_slice_mut,
127 self.read_capacity,
128 self.register_buffer,
129 )
130 }
131 .map(ReadBufState::Uninit)
132 .collect();
133
134 let buffers_state = BuffersState(buffers);
135
136 let io_uring = self.create_io_uring(buf_capacity)?;
137 let ring = Ring::new(io_uring, buffers_state);
138
139 if self.register_buffer {
140 unsafe { IoBufferChunk::register(buf_slice_mut, &ring)? };
143 }
144
145 if self.use_direct_io {
146 assert!(
149 self.read_capacity
150 .is_multiple_of(DIRECT_IO_READ_LEN_ALIGNMENT),
151 "read size is not aligned for direct IO({} is not a multiple of \
152 {DIRECT_IO_READ_LEN_ALIGNMENT})",
153 self.read_capacity
154 );
155 }
156
157 let open_file_flags = libc::O_NOATIME
158 | if self.use_direct_io {
159 libc::O_DIRECT
160 } else {
161 0
162 };
163
164 Ok(SequentialFileReader {
165 ring,
166 state: SequentialFileReaderState::default(),
167 open_file_flags,
168 backing_buffer: buffer,
169 _phantom: PhantomData,
170 })
171 }
172
173 fn create_io_uring(&self, buf_capacity: usize) -> io::Result<IoUring> {
174 let max_inflight_ops = (buf_capacity / self.read_capacity as usize) as u32;
176
177 let ring_squeue_size = self
183 .ring_squeue_size
184 .unwrap_or((max_inflight_ops / 2).max(1));
185 let ring = sqpoll::io_uring_builder_with(self.shared_sqpoll_fd)
187 .setup_cqsize(max_inflight_ops)
188 .build(ring_squeue_size)?;
189
190 ring.submitter()
193 .register_iowq_max_workers(&mut [self.max_iowq_workers, 1])?;
194 Ok(ring)
195 }
196}
197
198pub struct SequentialFileReader<'a> {
202 ring: Ring<BuffersState, ReadOp>,
205 open_file_flags: i32,
206 state: SequentialFileReaderState,
207 backing_buffer: PageAlignedMemory,
210 _phantom: PhantomData<&'a ()>,
211}
212
213impl<'a> SequentialFileReader<'a> {
214 pub fn set_path(&mut self, path: impl AsRef<Path>) -> io::Result<()> {
218 let file = OpenOptions::new()
219 .read(true)
220 .custom_flags(self.open_file_flags)
221 .open(path)?;
222 let file_size = file.metadata()?.len();
223 self.add_owned_file_to_prefetch(file, file_size)
224 }
225
226 pub fn add_owned_file_to_prefetch(
244 &mut self,
245 file: File,
246 read_limit: FileSize,
247 ) -> io::Result<()> {
248 self.add_file_by_fd(file.as_raw_fd(), read_limit)?;
249 self.state.owned_files.push_back(file);
250 Ok(())
251 }
252
253 pub fn rebind<'b>(mut self) -> io::Result<SequentialFileReader<'b>> {
257 while !self.state.files.is_empty() {
258 self.move_to_next_file()?;
259 }
260 Ok(SequentialFileReader {
261 ring: self.ring,
262 open_file_flags: self.open_file_flags,
263 state: self.state,
264 backing_buffer: self.backing_buffer,
265 _phantom: PhantomData,
266 })
267 }
268
269 fn add_file_by_fd(&mut self, fd: RawFd, read_limit: FileSize) -> io::Result<()> {
271 self.state.files.push_back(FileState::new(
273 fd,
274 self.open_file_flags & libc::O_DIRECT == libc::O_DIRECT,
275 read_limit,
276 ));
277
278 if self.state.all_buffers_used(self.ring.context()) {
279 return Ok(());
281 }
282
283 self.state.next_read_file_index =
285 Some(self.state.next_read_file_index.map_or(0, |idx| idx + 1));
286
287 self.try_schedule_new_ops()
289 }
290
291 fn move_to_next_file(&mut self) -> io::Result<()> {
293 let state = &mut self.state;
294
295 let Some(removed_file) = state.files.pop_front() else {
296 return Ok(());
297 };
298
299 state.current_offset = 0;
301 state.current_buf_pos = 0;
302 state.current_buf_remaining = 0;
303 state.left_to_consume = 0;
304
305 if removed_file.had_scheduled_reads() {
306 let sentinel_buf_index = state
311 .files
312 .iter()
313 .find_map(|f| f.start_buf_index)
314 .unwrap_or(state.next_read_buf_index);
315 let num_bufs = self.ring.context().len();
316 loop {
317 self.ring.process_completions()?;
318 let current_buf = self.ring.context_mut().get_mut(state.current_buf_index);
319 if current_buf.is_reading() {
320 self.ring.submit()?;
323 continue;
324 }
325 current_buf.transition_to_uninit();
326
327 let next_buf_index = (state.current_buf_index + 1) % num_bufs;
328 state.current_buf_index = next_buf_index;
329 if sentinel_buf_index == next_buf_index {
330 break;
331 }
332 }
333 }
334
335 if state
336 .owned_files
337 .front()
338 .is_some_and(|f| removed_file.is_same_file(f))
339 {
340 state.owned_files.pop_front();
341 }
342
343 if let Some(next_file_index) = state.next_read_file_index.as_mut() {
344 state.next_read_file_index = next_file_index.checked_sub(1);
346 if state.next_read_file_index.is_none() {
347 if state.files.is_empty() {
349 state.current_buf_index = 0;
351 state.next_read_buf_index = 0;
352 } else {
353 state.next_read_file_index = Some(0);
355 }
356 }
357 }
358
359 self.try_schedule_new_ops()
360 }
361
362 fn try_schedule_new_ops(&mut self) -> io::Result<()> {
363 while let Some(op) = self.state.next_read_op(self.ring.context_mut()) {
365 self.ring.push(op)?;
366 }
367 Ok(())
368 }
369
370 fn wait_current_buf_full(&mut self) -> io::Result<bool> {
371 if self
372 .state
373 .files
374 .front()
375 .is_none_or(|file| !file.had_scheduled_reads())
376 {
377 return Ok(false);
378 }
379 let num_bufs = self.ring.context().len();
380 loop {
381 self.ring.process_completions()?;
382
383 let state = &mut self.state;
384 let current_buf = &mut self.ring.context_mut().get_mut(state.current_buf_index);
385 match current_buf {
386 ReadBufState::Full { buf, eof_pos } => {
387 if state.current_buf_remaining == 0 && state.current_buf_pos == 0 {
388 state.current_buf_remaining = eof_pos.unwrap_or(buf.len());
390 if state.left_to_consume > 0 {
391 let consumed = state
393 .left_to_consume
394 .min(state.current_buf_remaining as usize);
395 state.left_to_consume -= consumed;
396 state.current_buf_pos += consumed as IoSize;
397 state.current_buf_remaining -= consumed as IoSize;
398 }
399 }
400
401 if state.current_buf_remaining > 0 {
403 return Ok(true);
405 }
406
407 if eof_pos.is_some() {
408 return Ok(false);
410 }
411 current_buf.transition_to_uninit();
413
414 state.move_to_next_buf(num_bufs);
416
417 self.try_schedule_new_ops()?;
419 }
420
421 ReadBufState::Reading => {
422 self.ring.submit()?
425 }
426
427 ReadBufState::Uninit(_) => unreachable!("should be initialized"),
428 }
429 }
431 }
432}
433
434impl<'a> Read for SequentialFileReader<'a> {
436 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
437 let available = self.fill_buf()?;
438 let bytes_to_read = available.len().min(buf.len());
439 if bytes_to_read == 0 {
440 return Ok(0); }
442 buf[..bytes_to_read].copy_from_slice(&available[..bytes_to_read]);
443 self.state.consume_in_current_buf(bytes_to_read);
444 Ok(bytes_to_read)
445 }
446
447 #[inline]
448 fn read_exact(&mut self, mut buf: &mut [u8]) -> io::Result<()> {
449 while !buf.is_empty() {
450 if self.state.current_buf_remaining == 0 && !self.wait_current_buf_full()? {
451 return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "read_exact"));
452 }
453 let current_buf = self.ring.context().get_fast(self.state.current_buf_index);
454 let to_copy_len = buf.len().min(self.state.current_buf_remaining as usize);
455 let slice = current_buf.slice(self.state.current_buf_pos, to_copy_len as IoSize);
456 self.state.consume_in_current_buf(to_copy_len);
457 let (to_fill, remaining) = unsafe { buf.split_at_mut_unchecked(to_copy_len) };
459 to_fill.copy_from_slice(slice);
460 buf = remaining;
461 }
462 Ok(())
463 }
464}
465
466impl<'a> BufRead for SequentialFileReader<'a> {
467 fn fill_buf(&mut self) -> io::Result<&[u8]> {
468 if self.state.current_buf_remaining == 0 && !self.wait_current_buf_full()? {
469 return Ok(&[]);
470 }
471
472 let current_buf = self.ring.context().get_fast(self.state.current_buf_index);
474 Ok(current_buf.slice(self.state.current_buf_pos, self.state.current_buf_remaining))
475 }
476
477 #[inline]
478 fn consume(&mut self, amt: usize) {
479 self.state.consume_in_current_buf(amt);
480 }
481}
482
483impl<'a> FileBufRead<'a> for SequentialFileReader<'a> {
484 fn set_file(&mut self, file: &'a File, read_limit: FileSize) -> io::Result<()> {
488 while self.state.files.front().is_some_and(|file_state| {
492 !file_state.is_same_file(file)
493 || file_state.read_limit != read_limit
494 || self.state.current_offset > 0
495 }) {
496 self.move_to_next_file()?;
497 }
498 if self.state.files.is_empty() {
499 self.add_file_to_prefetch(file, read_limit)?;
500 }
501 Ok(())
502 }
503
504 fn add_file_to_prefetch(&mut self, file: &'a File, read_limit: FileSize) -> io::Result<()> {
505 self.add_file_by_fd(file.as_raw_fd(), read_limit)
506 }
507
508 fn get_file_offset(&self) -> FileSize {
509 self.state.current_offset
510 }
511
512 fn consume_or_skip(&mut self, amt: usize) {
513 self.state.consume_or_skip(amt);
514 }
515}
516
517struct BuffersState(Box<[ReadBufState]>);
519
520impl BuffersState {
521 fn len(&self) -> u16 {
522 self.0.len() as u16
523 }
524
525 fn get_mut(&mut self, index: u16) -> &mut ReadBufState {
526 &mut self.0[index as usize]
527 }
528
529 #[inline]
530 fn get_fast(&self, index: u16) -> &ReadBufState {
531 debug_assert!(index < self.len());
532 unsafe { self.0.get_unchecked(index as usize) }
534 }
535}
536
537impl Deref for BuffersState {
538 type Target = [ReadBufState];
539
540 fn deref(&self) -> &Self::Target {
541 &self.0
542 }
543}
544
545impl DerefMut for BuffersState {
546 fn deref_mut(&mut self) -> &mut Self::Target {
547 &mut self.0
548 }
549}
550
551#[derive(Debug, Default)]
553struct SequentialFileReaderState {
554 files: VecDeque<FileState>,
558
559 left_to_consume: usize,
562 current_buf_index: u16,
564 current_buf_pos: IoSize,
566 current_buf_remaining: IoSize,
568 current_offset: FileSize,
570
571 next_read_file_index: Option<usize>,
573 next_read_buf_index: u16,
575
576 owned_files: VecDeque<File>,
577}
578
579impl SequentialFileReaderState {
580 #[inline]
581 fn consume_in_current_buf(&mut self, amt: usize) {
582 self.current_offset += amt as FileSize;
583 self.current_buf_pos += amt as IoSize;
584 self.current_buf_remaining -= amt as IoSize;
585 }
586
587 fn consume_or_skip(&mut self, amt: usize) {
588 if amt == 0 || self.files.is_empty() {
589 return;
590 }
591 self.current_offset += amt as FileSize;
592
593 let unconsumed_buf_len = self.current_buf_remaining as usize;
594 if let Some(new_remaining) = unconsumed_buf_len.checked_sub(amt) {
595 self.current_buf_pos += amt as IoSize;
596 self.current_buf_remaining = new_remaining as IoSize;
597 } else {
598 self.current_buf_pos += self.current_buf_remaining;
599 self.current_buf_remaining = 0;
600 self.left_to_consume += amt - unconsumed_buf_len;
603 }
604 }
605
606 fn next_read_op(&mut self, bufs: &mut [ReadBufState]) -> Option<ReadOp> {
613 if self.all_buffers_used(bufs) {
614 return None;
615 }
616 let num_bufs = bufs.len() as u16;
617 loop {
618 let read_file_index = self.next_read_file_index?;
619 match self.files[read_file_index].next_read_op(self.next_read_buf_index, bufs) {
620 Some(op) => {
621 self.next_read_buf_index = (self.next_read_buf_index + 1) % num_bufs;
622 return Some(op);
623 }
624 None => {
625 if read_file_index < self.files.len() - 1 {
627 self.next_read_file_index = Some(read_file_index + 1);
628 } else {
629 return None;
630 }
631 }
632 }
633 }
634 }
635
636 fn move_to_next_buf(&mut self, num_bufs: u16) {
637 self.current_buf_index = (self.current_buf_index + 1) % num_bufs;
638 self.current_buf_pos = 0;
639 self.current_buf_remaining = 0;
641 }
642
643 fn all_buffers_used(&self, bufs: &[ReadBufState]) -> bool {
645 bufs[self.next_read_buf_index as usize].is_used()
646 }
647}
648
649#[derive(Debug)]
651struct FileState {
652 raw_fd: RawFd,
653 is_direct_io: bool,
655 read_limit: FileSize,
657 next_read_offset: FileSize,
659 start_buf_index: Option<u16>,
661}
662
663impl FileState {
664 fn new(raw_fd: RawFd, is_direct_io: bool, read_limit: FileSize) -> Self {
665 Self {
666 raw_fd,
667 is_direct_io,
668 read_limit,
669 next_read_offset: 0,
670 start_buf_index: None,
671 }
672 }
673
674 fn is_same_file(&self, file: &File) -> bool {
675 self.raw_fd == file.as_raw_fd()
676 }
677
678 fn had_scheduled_reads(&self) -> bool {
679 self.start_buf_index.is_some()
680 }
681
682 fn next_read_op(&mut self, index: u16, bufs: &mut [ReadBufState]) -> Option<ReadOp> {
691 let Self {
692 start_buf_index,
693 raw_fd,
694 is_direct_io,
695 next_read_offset: offset,
696 read_limit,
697 } = self;
698 let left_to_read = read_limit.saturating_sub(*offset);
699 if left_to_read == 0 {
700 return None;
701 }
702
703 let buf = bufs[index as usize].transition_to_reading();
704
705 let read_len = left_to_read.min(buf.len() as FileSize);
706 let op = ReadOp {
707 fd: types::Fd(*raw_fd),
708 buf,
709 is_direct_io: *is_direct_io,
710 buf_offset: 0,
711 file_offset: *offset,
712 read_len: read_len as u32, is_last_read: left_to_read == read_len,
714 reader_buf_index: index,
715 };
716 if start_buf_index.is_none() {
718 *start_buf_index = Some(index);
719 }
720
721 *offset += read_len;
724
725 Some(op)
726 }
727}
728
729#[derive(Debug)]
731enum ReadBufState {
732 Uninit(IoBufferChunk),
735 Reading,
738 Full {
740 buf: IoBufferChunk,
741 eof_pos: Option<u32>,
743 },
744}
745
746impl ReadBufState {
747 fn is_used(&self) -> bool {
748 matches!(self, ReadBufState::Reading | ReadBufState::Full { .. })
749 }
750
751 fn is_reading(&self) -> bool {
752 matches!(self, ReadBufState::Reading)
753 }
754
755 #[inline]
756 fn slice(&self, start_pos: IoSize, len: IoSize) -> &[u8] {
757 match self {
758 Self::Full { buf, eof_pos } => {
759 debug_assert!(eof_pos.unwrap_or(buf.len()) >= start_pos + len);
760 unsafe { slice::from_raw_parts(buf.as_ptr().add(start_pos as usize), len as usize) }
763 }
764 Self::Uninit(_) | Self::Reading => {
765 unreachable!("must call as_slice only on full buffer")
766 }
767 }
768 }
769
770 fn transition_to_uninit(&mut self) {
772 match self {
773 Self::Uninit(_) => (),
774 Self::Reading => unreachable!("cannot reset a buffer that has pending read"),
775 Self::Full { buf, .. } => {
776 *self = ReadBufState::Uninit(mem::replace(buf, IoBufferChunk::empty()));
777 }
778 }
779 }
780
781 #[must_use]
783 fn transition_to_reading(&mut self) -> IoBufferChunk {
784 let Self::Uninit(buf) = mem::replace(self, Self::Reading) else {
785 unreachable!("buffer should be uninitialized")
786 };
787 buf
788 }
789}
790
791#[derive(Debug)]
792struct ReadOp {
793 fd: types::Fd,
794 buf: IoBufferChunk,
795 is_direct_io: bool,
796 buf_offset: IoSize,
800 file_offset: FileSize,
802 read_len: IoSize,
805 is_last_read: bool,
807 reader_buf_index: u16,
810}
811
812impl RingOp<BuffersState> for ReadOp {
813 fn entry(&mut self) -> squeue::Entry {
814 let ReadOp {
815 fd,
816 buf,
817 is_direct_io,
818 buf_offset,
819 file_offset,
820 read_len,
821 is_last_read: _,
822 reader_buf_index: _,
823 } = self;
824
825 let internal_read_len = if *is_direct_io && *read_len != buf.len() {
827 read_len
830 .next_multiple_of(DIRECT_IO_READ_LEN_ALIGNMENT)
831 .min(buf.len() - *buf_offset)
832 } else {
833 *read_len
834 };
835 debug_assert!(*buf_offset + internal_read_len <= buf.len());
836 let buf_ptr = unsafe { buf.as_mut_ptr().byte_add(*buf_offset as usize) };
838
839 let entry = match buf.io_buf_index() {
840 Some(io_buf_index) => {
841 opcode::ReadFixed::new(*fd, buf_ptr, internal_read_len, io_buf_index)
842 .offset(*file_offset)
843 .ioprio(IO_PRIO_BE_HIGHEST)
844 .build()
845 }
846 None => opcode::Read::new(*fd, buf_ptr, internal_read_len)
847 .offset(*file_offset)
848 .ioprio(IO_PRIO_BE_HIGHEST)
849 .build(),
850 };
851 entry.flags(squeue::Flags::ASYNC)
852 }
853
854 fn complete(
855 &mut self,
856 completion: &mut Completion<BuffersState, Self>,
857 res: io::Result<i32>,
858 ) -> io::Result<()> {
859 let ReadOp {
860 fd,
861 buf,
862 is_direct_io,
863 buf_offset,
864 file_offset,
865 read_len,
866 is_last_read,
867 reader_buf_index,
868 } = self;
869 let buffers = completion.context_mut();
870
871 let last_read_len = res? as IoSize;
872
873 let total_read_len = *buf_offset + last_read_len;
874 let buf = mem::replace(buf, IoBufferChunk::empty());
875
876 if last_read_len > 0 && last_read_len < *read_len {
877 let op: ReadOp = ReadOp {
879 fd: *fd,
880 buf,
881 is_direct_io: *is_direct_io,
882 buf_offset: total_read_len,
883 file_offset: *file_offset + last_read_len as FileSize,
884 read_len: *read_len - last_read_len,
885 reader_buf_index: *reader_buf_index,
886 is_last_read: *is_last_read,
887 };
888 completion.push(op)?;
892 } else {
893 buffers[*reader_buf_index as usize] = ReadBufState::Full {
894 buf,
895 eof_pos: (last_read_len == 0 || *is_last_read).then_some(total_read_len),
896 };
897 }
898
899 Ok(())
900 }
901}
902
903#[cfg(test)]
904mod tests {
905 use {super::*, std::io::Seek, tempfile::NamedTempFile};
906
907 fn write_test_pattern(num_bytes: usize, dst: &mut impl io::Write) -> Vec<u8> {
908 let pattern = (0..num_bytes).map(|i| i as u8).collect::<Vec<_>>();
909 io::Write::write_all(dst, &pattern).expect("must write prepared pattern");
910 pattern
911 }
912
913 fn read_as_vec(mut reader: impl Read) -> Vec<u8> {
914 let mut buf = Vec::new();
915 reader.read_to_end(&mut buf).unwrap();
916 buf
917 }
918
919 fn check_reading_file(
920 file_size: FileSize,
921 backing_buffer_size: usize,
922 read_capacity: IoSize,
923 use_direct_io: bool,
924 ) {
925 let pattern: Vec<u8> = (0..251).collect();
926
927 let mut temp_file = NamedTempFile::new().unwrap();
929 for _ in 0..file_size as usize / pattern.len() {
930 io::Write::write_all(&mut temp_file, &pattern).unwrap();
931 }
932 io::Write::write_all(
933 &mut temp_file,
934 &pattern[..file_size as usize % pattern.len()],
935 )
936 .unwrap();
937
938 let buf = PageAlignedMemory::new(backing_buffer_size).unwrap();
939 let mut reader = SequentialFileReaderBuilder::new()
940 .use_direct_io(use_direct_io)
941 .read_capacity(read_capacity)
942 .build_with_buffer(buf)
943 .unwrap();
944 reader.set_path(temp_file.path()).unwrap();
945
946 let all_read_data = read_as_vec(&mut reader);
948 assert_eq!(all_read_data.len() as FileSize, file_size);
949 assert_eq!(reader.get_file_offset(), file_size);
950
951 for (i, byte) in all_read_data.iter().enumerate() {
953 assert_eq!(*byte, pattern[i % pattern.len()], "Mismatch - pos {i}");
954 }
955 }
956
957 #[test]
958 fn test_reading_empty_file() {
959 check_reading_file(0, 4096, 1024, false);
960 }
961
962 #[test]
964 fn test_reading_small_file() {
965 check_reading_file(2500, 4096, 1024, false);
966 check_reading_file(2500, 4096, 2048, false);
967 check_reading_file(2500, 4096, 4096, false);
968 }
969
970 #[test]
972 fn test_reading_file_in_chunks() {
973 check_reading_file(25_000, 16384, 1024, false);
974 check_reading_file(25_000, 4096, 1024, false);
975 check_reading_file(25_000, 4096, 2048, false);
976 check_reading_file(25_000, 4096, 4096, false);
977 }
978
979 #[test]
981 fn test_reading_large_file() {
982 check_reading_file(250_000, 32768, 1024, false);
983 check_reading_file(250_000, 16384, 1024, false);
984 check_reading_file(250_000, 4096, 1024, false);
985 check_reading_file(250_000, 4096, 2048, false);
986 check_reading_file(250_000, 4096, 4096, false);
987 }
988
989 #[test]
990 fn test_non_registered_buffer_read() {
991 let file_size = 64 * 1024;
992 let mut temp_file = tempfile::NamedTempFile::new().unwrap();
993 let data = write_test_pattern(file_size, &mut temp_file);
994
995 let mut reader = SequentialFileReaderBuilder::new()
996 .read_capacity(4 * 1024)
997 .use_registered_buffers(false)
998 .build(16 * 1024)
999 .unwrap();
1000 reader.set_path(temp_file.path()).unwrap();
1001
1002 let mut all_read_data = Vec::new();
1003 reader.read_to_end(&mut all_read_data).unwrap();
1004 assert_eq!(all_read_data.len(), file_size);
1005 assert_eq!(all_read_data, data);
1006 }
1007
1008 #[test]
1009 fn test_add_file_ref() {
1010 let mut temp_file = NamedTempFile::new().unwrap();
1011 io::Write::write_all(&mut temp_file, &[0xa, 0xb, 0xc]).unwrap();
1012 temp_file.rewind().unwrap();
1013
1014 {
1015 let mut reader = SequentialFileReaderBuilder::new()
1016 .read_capacity(512)
1017 .build(1024)
1018 .unwrap();
1019 reader.add_file_to_prefetch(temp_file.as_file(), 3).unwrap();
1020 assert_eq!(read_as_vec(&mut reader), &[0xa, 0xb, 0xc]);
1021 }
1022 assert_eq!(read_as_vec(&mut temp_file), &[0xa, 0xb, 0xc]);
1024 }
1025
1026 #[test]
1027 fn test_direct_io_read() {
1028 check_reading_file(0, 4096, 4096, true);
1029 check_reading_file(2_500, 4096, 4096, true);
1030 check_reading_file(2_500, 16384, 4096, true);
1031 check_reading_file(25_000, 4096, 4096, true);
1032 check_reading_file(25_000, 16384, 4096, true);
1033 check_reading_file(250_000, 4096, 4096, true);
1034 check_reading_file(250_000, 16384, 4096, true);
1035 check_reading_file(4096, 4096, 4096, true);
1036 check_reading_file(4096, 16384, 4096, true);
1037 check_reading_file(16384, 4096, 4096, true);
1038 check_reading_file(16384, 16384, 4096, true);
1039 }
1040
1041 #[test]
1042 fn test_multiple_unlimited_files() {
1043 let mut temp1 = NamedTempFile::new().unwrap();
1044 io::Write::write_all(&mut temp1, &[0xa, 0xb, 0xc]).unwrap();
1045 let mut temp2 = NamedTempFile::new().unwrap();
1046 io::Write::write_all(&mut temp2, &[0xd, 0xe, 0xf, 0x10]).unwrap();
1047
1048 let mut reader = SequentialFileReaderBuilder::new()
1049 .read_capacity(512)
1050 .build(1024)
1051 .unwrap();
1052
1053 let f1 = File::open(temp1.path()).unwrap();
1054 let f2 = File::open(temp2.path()).unwrap();
1055 reader
1056 .add_owned_file_to_prefetch(f1, FileSize::MAX)
1057 .unwrap();
1058 reader
1059 .add_owned_file_to_prefetch(f2, FileSize::MAX)
1060 .unwrap();
1061
1062 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1063 reader.move_to_next_file().unwrap();
1064
1065 assert_eq!(read_as_vec(&mut reader), vec![0xd, 0xe, 0xf, 0x10]);
1066 reader.move_to_next_file().unwrap();
1067
1068 let f1 = File::open(temp1.path()).unwrap();
1069 reader
1070 .add_owned_file_to_prefetch(f1, FileSize::MAX)
1071 .unwrap();
1072 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1073 }
1074
1075 #[test]
1076 fn test_get_offset() {
1077 let mut temp1 = NamedTempFile::new().unwrap();
1078 write_test_pattern(600, &mut temp1);
1079
1080 let mut reader = SequentialFileReaderBuilder::new()
1081 .read_capacity(512)
1082 .build(1024)
1083 .unwrap();
1084 reader.add_file_to_prefetch(temp1.as_file(), 1990).unwrap();
1085
1086 assert_eq!(512, reader.fill_buf().unwrap().len());
1087 assert_eq!(0, reader.get_file_offset());
1088 reader.consume(0);
1089 assert_eq!(0, reader.get_file_offset());
1090
1091 reader.consume(40);
1092 assert_eq!(40, reader.get_file_offset());
1093 assert_eq!(472, reader.fill_buf().unwrap().len());
1094
1095 reader.consume(472);
1096 assert_eq!(512, reader.get_file_offset());
1097 assert_eq!(88, reader.fill_buf().unwrap().len());
1098 reader.consume(0);
1099 assert_eq!(512, reader.get_file_offset());
1100
1101 reader.consume(88);
1102 assert_eq!(600, reader.get_file_offset());
1103 assert_eq!(0, reader.fill_buf().unwrap().len());
1104
1105 reader.move_to_next_file().unwrap();
1106 assert_eq!(0, reader.get_file_offset());
1107 }
1108
1109 #[test]
1110 fn test_consume_skip_filled_buf_len() {
1111 let mut temp1 = NamedTempFile::new().unwrap();
1112 let pattern = write_test_pattern(6000, &mut temp1);
1113
1114 let mut reader = SequentialFileReaderBuilder::new()
1115 .read_capacity(512)
1116 .build(2048)
1117 .unwrap();
1118 reader.add_file_to_prefetch(temp1.as_file(), 5990).unwrap();
1119
1120 assert_eq!(reader.fill_buf().unwrap(), &pattern[..512]);
1121 assert_eq!(0, reader.get_file_offset());
1122
1123 reader.consume_or_skip(600);
1124 assert_eq!(600, reader.get_file_offset());
1125 assert_eq!(reader.fill_buf().unwrap(), &pattern[600..1024]);
1126
1127 reader.consume_or_skip(400);
1128 assert_eq!(1000, reader.get_file_offset());
1129 assert_eq!(reader.fill_buf().unwrap(), &pattern[1000..1024]);
1130
1131 reader.consume_or_skip(25);
1132 assert_eq!(reader.fill_buf().unwrap(), &pattern[1025..1536]);
1133
1134 reader.consume_or_skip(2000);
1135 assert_eq!(reader.fill_buf().unwrap(), &pattern[3025..3072]);
1136 }
1137
1138 #[test]
1139 fn test_set_file() {
1140 let mut temp1 = NamedTempFile::new().unwrap();
1141 io::Write::write_all(&mut temp1, &[0xa, 0xb, 0xc]).unwrap();
1142 let mut temp2 = NamedTempFile::new().unwrap();
1143 io::Write::write_all(&mut temp2, &[0xd, 0xe, 0xf, 0x10]).unwrap();
1144
1145 let mut reader = SequentialFileReaderBuilder::new()
1146 .read_capacity(512)
1147 .build(1024)
1148 .unwrap();
1149 reader.add_file_to_prefetch(temp1.as_file(), 3).unwrap();
1150 reader.add_file_to_prefetch(temp2.as_file(), 4).unwrap();
1151
1152 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1153
1154 reader.set_file(temp2.as_file(), 4).unwrap();
1155 assert_eq!(read_as_vec(&mut reader), vec![0xd, 0xe, 0xf, 0x10]);
1156
1157 reader.set_file(temp1.as_file(), 4).unwrap();
1158 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1159
1160 let f1 = File::open(temp1.path()).unwrap();
1161 reader
1162 .add_owned_file_to_prefetch(f1, FileSize::MAX)
1163 .unwrap();
1164 reader.move_to_next_file().unwrap();
1165 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1166
1167 reader.set_file(temp2.as_file(), 4).unwrap();
1168 assert_eq!(read_as_vec(&mut reader), vec![0xd, 0xe, 0xf, 0x10]);
1169
1170 reader.set_file(temp2.as_file(), 4).unwrap();
1172 assert_eq!(reader.get_file_offset(), 0);
1173 assert_eq!(read_as_vec(&mut reader), vec![0xd, 0xe, 0xf, 0x10]);
1174
1175 reader.set_file(temp2.as_file(), 2).unwrap();
1178 assert_eq!(reader.get_file_offset(), 0);
1179 reader.set_file(temp2.as_file(), 3).unwrap();
1182 assert_eq!(reader.get_file_offset(), 0);
1183 assert_eq!(read_as_vec(&mut reader), vec![0xd, 0xe, 0xf]);
1184 }
1185
1186 #[test]
1187 fn test_multiple_files_including_zero_limit() {
1188 let mut temp1 = NamedTempFile::new().unwrap();
1189 io::Write::write_all(&mut temp1, &[0xa, 0xb, 0xc]).unwrap();
1190 let mut temp2 = NamedTempFile::new().unwrap();
1191 io::Write::write_all(&mut temp2, &[0xd, 0xe, 0xf, 0x10]).unwrap();
1192
1193 let mut reader = SequentialFileReaderBuilder::new()
1194 .read_capacity(512)
1195 .build(1024)
1196 .unwrap();
1197
1198 reader.add_file_to_prefetch(temp1.as_file(), 3).unwrap();
1199 reader.add_file_to_prefetch(temp2.as_file(), 0).unwrap();
1200 reader.add_file_to_prefetch(temp1.as_file(), 10).unwrap();
1201
1202 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1203
1204 reader.move_to_next_file().unwrap();
1205 assert_eq!(read_as_vec(&mut reader), vec![]);
1206
1207 reader.move_to_next_file().unwrap();
1208 assert_eq!(read_as_vec(&mut reader), vec![0xa, 0xb, 0xc]);
1209
1210 reader.add_file_to_prefetch(temp1.as_file(), 0).unwrap();
1211 reader.move_to_next_file().unwrap();
1212 assert_eq!(read_as_vec(&mut reader), vec![]);
1213
1214 reader.add_file_to_prefetch(temp2.as_file(), 4).unwrap();
1215 reader.move_to_next_file().unwrap();
1216 assert_eq!(read_as_vec(&mut reader), vec![0xd, 0xe, 0xf, 0x10]);
1217 }
1218
1219 #[test]
1220 fn test_read_exact() {
1221 let mut temp1 = NamedTempFile::new().unwrap();
1222 let pattern = write_test_pattern(6000, &mut temp1);
1223
1224 let mut reader = SequentialFileReaderBuilder::new()
1225 .read_capacity(512)
1226 .build(2048)
1227 .unwrap();
1228 reader.add_file_to_prefetch(temp1.as_file(), 6000).unwrap();
1229
1230 let mut buf = [0u8; 100];
1232 reader.read_exact(&mut buf).unwrap();
1233 assert_eq!(buf, pattern[..100]);
1234
1235 reader.read_exact(&mut []).unwrap();
1237 assert_eq!(reader.get_file_offset(), 100);
1238
1239 let mut buf = vec![0u8; 2000];
1241 reader.read_exact(&mut buf).unwrap();
1242 assert_eq!(buf, pattern[100..2100]);
1243
1244 let mut buf = vec![0u8; 3900];
1246 reader.read_exact(&mut buf).unwrap();
1247 assert_eq!(buf, pattern[2100..6000]);
1248
1249 let err = reader.read_exact(&mut [0u8; 1]).unwrap_err();
1251 assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
1252 }
1253}