use std::mem::{align_of, size_of};
use std::path::Path;
use std::sync::{LazyLock, OnceLock};
use std::{io, mem, ptr};
use fs_err as fs;
use fs_err::{File, OpenOptions};
use memmap2::{Mmap, MmapMut};
use super::advice::{AdviceSetting, Madviseable, madvise};
pub const TEMP_FILE_EXTENSION: &str = "tmp";
pub static MULTI_MMAP_IS_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
let mut supported = true;
match MULTI_MMAP_SUPPORT_CHECK_RESULT.get() {
Some(true) => {}
Some(false) => {
log::warn!(
"Not using multi-mmap due to limited support, you may see reduced performance",
);
supported = false;
}
None => {
log::warn!(
"MULTI_MMAP_SUPPORT_CHECK_RESULT should be initialized before accessing MULTI_MMAP_IS_SUPPORTED"
);
}
}
if supported && std::env::var_os("QDRANT_NO_MULTI_MMAP").is_some_and(|val| !val.is_empty()) {
supported = false;
log::warn!(
"Not using multi-mmap because QDRANT_NO_MULTI_MMAP is set, you may see reduced performance"
);
}
supported
});
pub static MULTI_MMAP_SUPPORT_CHECK_RESULT: OnceLock<bool> = OnceLock::new();
pub fn create_and_ensure_length(path: &Path, length: usize) -> io::Result<File> {
if path.exists() {
let file = OpenOptions::new()
.read(true)
.write(true)
.truncate(false)
.open(path)?;
file.set_len(length as u64)?;
Ok(file)
} else {
let temp_path = path.with_extension(TEMP_FILE_EXTENSION);
{
let temp_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&temp_path)?;
temp_file.set_len(length as u64)?;
}
fs::rename(&temp_path, path)?;
OpenOptions::new().read(true).write(true).open(path)
}
}
pub fn open_read_mmap(path: &Path, advice: AdviceSetting, populate: bool) -> io::Result<Mmap> {
let file = OpenOptions::new().read(true).open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
if populate {
mmap.populate();
}
madvise(&mmap, advice.resolve())?;
Ok(mmap)
}
pub fn open_write_mmap(path: &Path, advice: AdviceSetting, populate: bool) -> io::Result<MmapMut> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
let mmap = unsafe { MmapMut::map_mut(&file)? };
if populate {
mmap.populate();
}
madvise(&mmap, advice.resolve())?;
Ok(mmap)
}
#[deprecated = "use `bytemuck` or `zerocopy`"]
pub unsafe fn transmute_from_u8<T>(v: &[u8]) -> &T {
debug_assert_eq!(v.len(), size_of::<T>());
debug_assert_eq!(
v.as_ptr().align_offset(align_of::<T>()),
0,
"transmuting byte slice {:p} into {}: \
required alignment is {} bytes, \
byte slice misaligned by {} bytes",
v.as_ptr(),
std::any::type_name::<T>(),
align_of::<T>(),
v.as_ptr().align_offset(align_of::<T>()),
);
unsafe { &*v.as_ptr().cast::<T>() }
}
#[deprecated = "use `bytemuck` or `zerocopy`"]
pub unsafe fn transmute_to_u8<T: Sized>(v: &T) -> &[u8] {
unsafe { std::slice::from_raw_parts(ptr::from_ref::<T>(v).cast::<u8>(), mem::size_of_val(v)) }
}
#[deprecated = "use `bytemuck` or `zerocopy`"]
pub unsafe fn transmute_from_u8_to_slice<T>(data: &[u8]) -> &[T] {
debug_assert_eq!(data.len() % size_of::<T>(), 0);
debug_assert_eq!(
data.as_ptr().align_offset(align_of::<T>()),
0,
"transmuting byte slice {:p} into slice of {}: \
required alignment is {} bytes, \
byte slice misaligned by {} bytes",
data.as_ptr(),
std::any::type_name::<T>(),
align_of::<T>(),
data.as_ptr().align_offset(align_of::<T>()),
);
let len = data.len() / size_of::<T>();
let ptr = data.as_ptr().cast::<T>();
unsafe { std::slice::from_raw_parts(ptr, len) }
}
#[deprecated = "use `bytemuck` or `zerocopy`"]
pub unsafe fn transmute_to_u8_slice<T>(v: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(v.as_ptr().cast::<u8>(), mem::size_of_val(v)) }
}