use crate::errors::{MmapIoError, Result};
use crate::mmap::{MapVariant, MappedSlice, MemoryMappedFile};
use crate::utils::page_size;
use memmap2::MmapMut;
use parking_lot::RwLockReadGuard;
use std::marker::PhantomData;
#[allow(dead_code)]
enum IterGuard<'a> {
Held(RwLockReadGuard<'a, MmapMut>),
None,
}
pub struct ChunkIterator<'a> {
base: *const u8,
total_len: usize,
chunk_size: usize,
current_offset: usize,
_guard: IterGuard<'a>,
_marker: PhantomData<&'a [u8]>,
}
unsafe impl<'a> Send for ChunkIterator<'a> {}
unsafe impl<'a> Sync for ChunkIterator<'a> {}
impl<'a> ChunkIterator<'a> {
pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
let total_len = usize::try_from(mmap.current_len()?)
.map_err(|_| MmapIoError::ResizeFailed("mapping length exceeds usize::MAX".into()))?;
let (base, guard) = match &mmap.inner.map {
MapVariant::Ro(m) => (m.as_ptr(), IterGuard::None),
MapVariant::Rw(lock) => {
let g = lock.read();
let ptr = g.as_ptr();
(ptr, IterGuard::Held(g))
}
MapVariant::Cow(m) => (m.as_ptr(), IterGuard::None),
};
Ok(Self {
base,
total_len,
chunk_size,
current_offset: 0,
_guard: guard,
_marker: PhantomData,
})
}
}
impl<'a> Iterator for ChunkIterator<'a> {
type Item = MappedSlice<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.chunk_size == 0 || self.current_offset >= self.total_len {
return None;
}
let remaining = self.total_len - self.current_offset;
let chunk_len = remaining.min(self.chunk_size);
let slice: &'a [u8] =
unsafe { std::slice::from_raw_parts(self.base.add(self.current_offset), chunk_len) };
self.current_offset += chunk_len;
Some(MappedSlice::owned(slice))
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.chunk_size == 0 {
return (0, Some(0));
}
let remaining = self.total_len.saturating_sub(self.current_offset);
let chunks = remaining.div_ceil(self.chunk_size);
(chunks, Some(chunks))
}
}
impl<'a> ExactSizeIterator for ChunkIterator<'a> {}
pub struct PageIterator<'a> {
inner: ChunkIterator<'a>,
}
impl<'a> PageIterator<'a> {
pub(crate) fn new(mmap: &'a MemoryMappedFile) -> Result<Self> {
let ps = page_size();
Ok(Self {
inner: ChunkIterator::new(mmap, ps)?,
})
}
}
impl<'a> Iterator for PageIterator<'a> {
type Item = MappedSlice<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<'a> ExactSizeIterator for PageIterator<'a> {}
pub struct ChunkIteratorOwned<'a> {
inner: ChunkIterator<'a>,
}
impl<'a> ChunkIteratorOwned<'a> {
pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
Ok(Self {
inner: ChunkIterator::new(mmap, chunk_size)?,
})
}
}
impl<'a> Iterator for ChunkIteratorOwned<'a> {
type Item = Result<Vec<u8>>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|slice| Ok(slice.as_slice().to_vec()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<'a> ExactSizeIterator for ChunkIteratorOwned<'a> {}
pub struct PageIteratorOwned<'a> {
inner: PageIterator<'a>,
}
impl<'a> PageIteratorOwned<'a> {
pub(crate) fn new(mmap: &'a MemoryMappedFile) -> Result<Self> {
Ok(Self {
inner: PageIterator::new(mmap)?,
})
}
}
impl<'a> Iterator for PageIteratorOwned<'a> {
type Item = Result<Vec<u8>>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|slice| Ok(slice.as_slice().to_vec()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<'a> ExactSizeIterator for PageIteratorOwned<'a> {}
pub struct ChunkIteratorMut<'a> {
mmap: &'a MemoryMappedFile,
chunk_size: usize,
total_len: u64,
_phantom: PhantomData<&'a mut [u8]>,
}
impl<'a> ChunkIteratorMut<'a> {
pub(crate) fn new(mmap: &'a MemoryMappedFile, chunk_size: usize) -> Result<Self> {
let total_len = mmap.current_len()?;
Ok(Self {
mmap,
chunk_size,
total_len,
_phantom: PhantomData,
})
}
pub fn for_each_mut<F>(self, mut f: F) -> Result<()>
where
F: FnMut(u64, &mut [u8]) -> Result<()>,
{
if self.chunk_size == 0 || self.total_len == 0 {
return Ok(());
}
match &self.mmap.inner.map {
MapVariant::Ro(_) => Err(MmapIoError::InvalidMode(
"chunks_mut requires ReadWrite mode",
)),
MapVariant::Cow(_) => Err(MmapIoError::InvalidMode(
"chunks_mut on copy-on-write mapping is not supported (phase-1 read-only)",
)),
MapVariant::Rw(lock) => {
let mut guard = lock.write();
let total = self.total_len as usize;
let chunk_size = self.chunk_size;
let mut offset = 0usize;
while offset < total {
let remaining = total - offset;
let chunk_len = remaining.min(chunk_size);
let end = offset + chunk_len;
f(offset as u64, &mut guard[offset..end])?;
offset = end;
}
Ok(())
}
}
}
}
impl MemoryMappedFile {
#[cfg(feature = "iterator")]
#[must_use]
pub fn chunks(&self, chunk_size: usize) -> ChunkIterator<'_> {
ChunkIterator::new(self, chunk_size).expect("chunk iterator creation should not fail")
}
#[cfg(feature = "iterator")]
#[must_use]
pub fn pages(&self) -> PageIterator<'_> {
PageIterator::new(self).expect("page iterator creation should not fail")
}
#[cfg(feature = "iterator")]
#[must_use]
pub fn chunks_owned(&self, chunk_size: usize) -> ChunkIteratorOwned<'_> {
ChunkIteratorOwned::new(self, chunk_size)
.expect("owned chunk iterator creation should not fail")
}
#[cfg(feature = "iterator")]
#[must_use]
pub fn pages_owned(&self) -> PageIteratorOwned<'_> {
PageIteratorOwned::new(self).expect("owned page iterator creation should not fail")
}
#[cfg(feature = "iterator")]
#[must_use]
pub fn chunks_mut(&self, chunk_size: usize) -> ChunkIteratorMut<'_> {
ChunkIteratorMut::new(self, chunk_size)
.expect("mutable chunk iterator creation should not fail")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::create_mmap;
use std::fs;
use std::path::PathBuf;
fn tmp_path(name: &str) -> PathBuf {
let mut p = std::env::temp_dir();
p.push(format!(
"mmap_io_iterator_test_{}_{}",
name,
std::process::id()
));
p
}
#[test]
#[cfg(feature = "iterator")]
fn test_chunk_iterator_zero_copy() {
let path = tmp_path("chunk_iter");
let _ = fs::remove_file(&path);
let mmap = create_mmap(&path, 10240).expect("create");
for i in 0..10 {
let data = vec![i as u8; 1024];
mmap.update_region(i * 1024, &data).expect("write");
}
mmap.flush().expect("flush");
let chunks: Vec<Vec<u8>> = mmap.chunks(1024).map(|s| s.as_slice().to_vec()).collect();
assert_eq!(chunks.len(), 10);
for (i, chunk) in chunks.iter().enumerate() {
assert_eq!(chunk.len(), 1024);
assert!(chunk.iter().all(|&b| b == i as u8));
}
let chunks: Vec<Vec<u8>> = mmap.chunks(3000).map(|s| s.as_slice().to_vec()).collect();
assert_eq!(chunks.len(), 4);
assert_eq!(chunks[3].len(), 1240);
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
#[test]
#[cfg(feature = "iterator")]
fn test_page_iterator_zero_copy() {
let path = tmp_path("page_iter");
let _ = fs::remove_file(&path);
let ps = page_size();
let file_size = ps * 3 + 100;
let mmap = create_mmap(&path, file_size as u64).expect("create");
let pages: Vec<usize> = mmap.pages().map(|p| p.len()).collect();
assert_eq!(pages.len(), 4);
assert_eq!(pages[0], ps);
assert_eq!(pages[1], ps);
assert_eq!(pages[2], ps);
assert_eq!(pages[3], 100);
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
#[test]
#[cfg(feature = "iterator")]
fn test_chunks_owned_compat() {
let path = tmp_path("chunks_owned");
let _ = fs::remove_file(&path);
let mmap = create_mmap(&path, 4096).expect("create");
mmap.update_region(0, &vec![0x11u8; 4096]).expect("write");
let owned: Vec<Vec<u8>> = mmap
.chunks_owned(1024)
.collect::<Result<Vec<_>>>()
.expect("collect");
assert_eq!(owned.len(), 4);
for v in &owned {
assert_eq!(v.len(), 1024);
assert!(v.iter().all(|&b| b == 0x11));
}
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
#[test]
#[cfg(feature = "iterator")]
fn test_mutable_chunk_iterator_single_guard() {
let path = tmp_path("mut_chunk_iter");
let _ = fs::remove_file(&path);
let mmap = create_mmap(&path, 4096).expect("create");
mmap.chunks_mut(1024)
.for_each_mut(|offset, chunk| {
let value = (offset / 1024) as u8;
chunk.fill(value);
Ok(())
})
.expect("for_each_mut");
mmap.flush().expect("flush");
let mut buf = [0u8; 1024];
for i in 0..4 {
mmap.read_into(i * 1024, &mut buf).expect("read");
assert!(buf.iter().all(|&b| b == i as u8));
}
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
#[test]
#[cfg(feature = "iterator")]
fn test_iterator_size_hint() {
let path = tmp_path("size_hint");
let _ = fs::remove_file(&path);
let mmap = create_mmap(&path, 10000).expect("create");
{
let iter = mmap.chunks(1000);
assert_eq!(iter.size_hint(), (10, Some(10)));
}
{
let iter = mmap.chunks(3000);
assert_eq!(iter.size_hint(), (4, Some(4)));
}
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
#[test]
#[cfg(feature = "iterator")]
fn test_iterator_zero_chunk_size_yields_nothing() {
let path = tmp_path("zero_chunk");
let _ = fs::remove_file(&path);
let mmap = create_mmap(&path, 4096).expect("create");
assert_eq!(mmap.chunks(0).count(), 0);
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
#[test]
#[cfg(feature = "iterator")]
fn test_one_byte_file_iteration() {
let path = tmp_path("one_byte_iter");
let _ = fs::remove_file(&path);
let mmap = create_mmap(&path, 1).expect("create");
let chunks: Vec<usize> = mmap.chunks(1024).map(|s| s.len()).collect();
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], 1);
drop(mmap);
fs::remove_file(&path).expect("cleanup");
}
}