#![allow(unsafe_code)]
use std::io::Write;
use std::ops::Deref;
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
use memmap2::Mmap;
use tempfile::tempfile;
use crate::Result;
use crate::errors::PagedbError;
pub struct MmapView {
_map: Mmap,
data: *const u8,
len: usize,
budget_used: Arc<AtomicU64>,
charged: u64,
}
unsafe impl Send for MmapView {}
unsafe impl Sync for MmapView {}
impl MmapView {
pub(crate) fn from_pages(
pages: &[&[u8]],
budget_used: Arc<AtomicU64>,
budget_limit: u64,
) -> Result<Self> {
let total: usize = pages.iter().map(|p| p.len()).sum();
let total_u64 = u64::try_from(total)
.map_err(|_| PagedbError::Io(std::io::Error::other("extent too large")))?;
loop {
let current = budget_used.load(Ordering::Acquire);
let new_total = current.saturating_add(total_u64);
if new_total > budget_limit {
return Err(PagedbError::MmapViewQuotaExceeded {
segment_bytes: total_u64,
available_bytes: budget_limit.saturating_sub(current),
});
}
if budget_used
.compare_exchange(current, new_total, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
break;
}
}
let map = (|| -> std::io::Result<Mmap> {
let mut f = tempfile()?;
for page in pages {
f.write_all(page)?;
}
f.flush()?;
let map = unsafe { Mmap::map(&f)? };
Ok(map)
})()
.map_err(|e| {
budget_used.fetch_sub(total_u64, Ordering::AcqRel);
PagedbError::Io(e)
})?;
let data = map.as_ptr();
let len = map.len();
Ok(Self {
_map: map,
data,
len,
budget_used,
charged: total_u64,
})
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.data, self.len) }
}
}
impl Deref for MmapView {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.as_slice()
}
}
impl Drop for MmapView {
fn drop(&mut self) {
self.budget_used.fetch_sub(self.charged, Ordering::AcqRel);
}
}
impl std::fmt::Debug for MmapView {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MmapView")
.field("len", &self.len)
.field("charged_bytes", &self.charged)
.finish_non_exhaustive()
}
}