use super::tree::directory::TreeError;
use crate::lifecycle::QueryControl;
use crate::property_graph::staging::{WriteMemory, WriteReservation};
use std::cell::Cell;
pub const MAX_STORAGE_PREPARE_BYTES: usize = 32 * 1024 * 1024;
pub struct StorageMemory<'a> {
writer: &'a WriteMemory<'a>,
control: &'a QueryControl,
limit: usize,
used: Cell<usize>,
peak: Cell<usize>,
_reservation: WriteReservation<'a>,
}
impl<'a> StorageMemory<'a> {
pub fn new(
writer: &'a WriteMemory<'a>,
control: &'a QueryControl,
limit: usize,
) -> Result<Self, TreeError> {
control.checkpoint().map_err(TreeError::Control)?;
let bytes = std::mem::size_of::<Self>();
if limit > MAX_STORAGE_PREPARE_BYTES || bytes > limit {
return Err(TreeError::Memory);
}
let reservation = writer
.reserve(bytes, &mut |_| Ok(()))
.map_err(|_| TreeError::Memory)?;
Ok(Self {
writer,
control,
limit,
used: Cell::new(bytes),
peak: Cell::new(bytes),
_reservation: reservation,
})
}
pub(super) fn require_batch(
&self,
batch: &crate::property_graph::staging::StagedBatch<'_>,
) -> Result<(), TreeError> {
if !batch.uses_memory(self.writer) {
return Err(TreeError::Invalid("staging/storage writer owner mismatch"));
}
Ok(())
}
pub fn reserved_bytes(&self) -> usize {
self.used.get()
}
pub fn peak_reserved_bytes(&self) -> usize {
self.peak.get()
}
pub(super) fn control(&self) -> &'a QueryControl {
self.control
}
pub(super) const fn resources(&self) -> &'a crate::property_graph::resources::GraphResources {
self.writer.resources()
}
pub(crate) fn reserve(&self, bytes: usize) -> Result<StorageReservation<'_>, TreeError> {
self.control.checkpoint().map_err(TreeError::Control)?;
let next = self
.used
.get()
.checked_add(bytes)
.ok_or(TreeError::Memory)?;
if next > self.limit {
return Err(TreeError::Memory);
}
let writer = self
.writer
.reserve(bytes, &mut |_| Ok(()))
.map_err(|_| TreeError::Memory)?;
self.used.set(next);
self.peak.set(self.peak.get().max(next));
Ok(StorageReservation {
memory: self,
writer,
bytes,
})
}
}
pub(crate) struct StorageReservation<'a> {
memory: &'a StorageMemory<'a>,
writer: WriteReservation<'a>,
bytes: usize,
}
impl StorageReservation<'_> {
pub(crate) fn bytes(&self) -> usize {
self.bytes
}
pub(crate) fn resize(&mut self, bytes: usize) -> Result<(), TreeError> {
self.memory
.control
.checkpoint()
.map_err(TreeError::Control)?;
let next = self
.memory
.used
.get()
.checked_sub(self.bytes)
.and_then(|n| n.checked_add(bytes))
.ok_or(TreeError::Memory)?;
if next > self.memory.limit {
return Err(TreeError::Memory);
}
self.writer.resize(bytes).map_err(|_| TreeError::Memory)?;
self.memory.used.set(next);
self.memory.peak.set(self.memory.peak.get().max(next));
self.bytes = bytes;
Ok(())
}
}
impl Drop for StorageReservation<'_> {
fn drop(&mut self) {
self.memory
.used
.set(self.memory.used.get().saturating_sub(self.bytes));
}
}
pub struct StorageBuffer<'a, T> {
values: Vec<T>,
reservation: StorageReservation<'a>,
limit: usize,
}
impl<'a, T> StorageBuffer<'a, T> {
pub fn new(memory: &'a StorageMemory<'a>, capacity: usize) -> Result<Self, TreeError> {
let bytes = capacity
.checked_mul(std::mem::size_of::<T>())
.ok_or(TreeError::Memory)?;
let mut reservation = memory.reserve(bytes)?;
let mut values = Vec::new();
#[cfg(feature = "allocation-audit")]
let allocation = crate::allocation_audit::attributed(|| values.try_reserve_exact(capacity));
#[cfg(not(feature = "allocation-audit"))]
let allocation = values.try_reserve_exact(capacity);
allocation.map_err(|_| TreeError::Memory)?;
let actual = values
.capacity()
.checked_mul(std::mem::size_of::<T>())
.ok_or(TreeError::Memory)?;
reservation.resize(actual)?;
Ok(Self {
values,
reservation,
limit: capacity,
})
}
pub(crate) fn drain(&mut self) -> impl Iterator<Item = T> + '_ {
self.values.drain(..)
}
pub fn owned_bytes(&self) -> usize {
self.reservation.bytes()
}
pub const fn capacity(&self) -> usize {
self.limit
}
pub fn as_slice(&self) -> &[T] {
&self.values
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.values
}
pub fn push(&mut self, value: T) -> Result<(), TreeError> {
if self.values.len() == self.limit {
#[cfg(all(test, feature = "graph-cypher"))]
super::preparation_work_capture::buffer_refusal(std::any::type_name::<T>(), self.limit);
return Err(TreeError::Memory);
}
self.values.push(value);
Ok(())
}
}
impl<T: Copy> StorageBuffer<'_, T> {
pub(crate) fn extend_from_slice(&mut self, input: &[T]) -> Result<(), TreeError> {
if self
.values
.len()
.checked_add(input.len())
.is_none_or(|n| n > self.limit)
{
return Err(TreeError::Memory);
}
self.values.extend_from_slice(input);
Ok(())
}
}