use super::MAX_QUERY_BYTES;
use crate::lifecycle::StoreError;
use crate::property_graph::resources::{GraphReservation, GraphResources};
use std::cell::Cell;
mod inputs;
pub use inputs::{QueryInputs, RetainedAllocation, RetentionInventory, RuntimePlan};
#[derive(Debug)]
pub enum MemoryError {
Limit,
Allocation,
Store(StoreError),
UnprovedInput,
Value(super::QueryError),
Plan(super::plan::PlanError),
}
impl From<super::QueryError> for MemoryError {
fn from(error: super::QueryError) -> Self {
Self::Value(error)
}
}
impl From<StoreError> for MemoryError {
fn from(error: StoreError) -> Self {
Self::Store(error)
}
}
impl std::fmt::Display for MemoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Limit => f.write_str("graph query memory limit"),
Self::Allocation => f.write_str("graph query allocation failed"),
Self::Store(e) => e.fmt(f),
Self::UnprovedInput => f.write_str("unproved query input ownership"),
Self::Value(e) => e.fmt(f),
Self::Plan(e) => e.fmt(f),
}
}
}
impl std::error::Error for MemoryError {}
pub struct QueryMemory<'g> {
shared: &'g GraphResources,
limit: usize,
used: Cell<usize>,
peak: Cell<usize>,
_control: GraphReservation,
}
impl<'g> QueryMemory<'g> {
pub fn new(shared: &'g GraphResources, limit: usize) -> Result<Self, MemoryError> {
let bytes = std::mem::size_of::<Self>();
if limit > MAX_QUERY_BYTES || bytes > limit {
return Err(MemoryError::Limit);
}
let control = shared.reserve(bytes)?;
Ok(Self {
shared,
limit,
used: Cell::new(bytes),
peak: Cell::new(bytes),
_control: control,
})
}
pub fn reserve_application(
&self,
bytes: usize,
) -> Result<crate::property_graph::resources::GraphApplicationReservation, MemoryError> {
self.shared
.reserve_application(bytes)
.map_err(MemoryError::from)
}
#[allow(
clippy::result_large_err,
reason = "allocation failure must return the original charge without allocating an error box"
)]
pub fn adopt_shared<'m>(
&'m self,
backing: GraphReservation,
) -> Result<QuerySharedReservation<'m, 'g>, (MemoryError, GraphReservation)> {
let prepared = (|| {
if !backing.belongs_to(self.shared) {
return Err(MemoryError::UnprovedInput);
}
let bytes = usize::try_from(backing.bytes()).map_err(|_| MemoryError::Limit)?;
let control = self.reserve(std::mem::size_of::<QuerySharedReservation<'_, '_>>())?;
let total = self
.used
.get()
.checked_add(bytes)
.ok_or(MemoryError::Limit)?;
if total > self.limit {
return Err(MemoryError::Limit);
}
Ok((bytes, total, control))
})();
match prepared {
Err(error) => Err((error, backing)),
Ok((bytes, total, control)) => {
self.used.set(total);
self.peak.set(self.peak.get().max(total));
Ok(QuerySharedReservation {
shared: backing,
memory: self,
bytes,
_control: control,
})
}
}
}
pub fn reserved_bytes(&self) -> usize {
self.used.get()
}
pub fn peak_reserved_bytes(&self) -> usize {
self.peak.get()
}
pub(crate) fn reserve(&self, bytes: usize) -> Result<QueryReservation<'_, 'g>, MemoryError> {
let total = self
.used
.get()
.checked_add(bytes)
.ok_or(MemoryError::Limit)?;
if total > self.limit {
return Err(MemoryError::Limit);
}
let shared = self.shared.reserve(bytes)?;
self.used.set(total);
self.peak.set(self.peak.get().max(total));
Ok(QueryReservation {
memory: self,
shared,
bytes,
})
}
}
pub struct QuerySharedReservation<'m, 'g> {
shared: GraphReservation,
memory: &'m QueryMemory<'g>,
bytes: usize,
_control: QueryReservation<'m, 'g>,
}
impl QuerySharedReservation<'_, '_> {
pub fn bytes(&self) -> u64 {
self.shared.bytes()
}
}
impl Drop for QuerySharedReservation<'_, '_> {
fn drop(&mut self) {
self.memory
.used
.set(self.memory.used.get().saturating_sub(self.bytes));
}
}
pub(crate) struct QueryReservation<'m, 'g> {
memory: &'m QueryMemory<'g>,
shared: GraphReservation,
bytes: usize,
}
impl<'m, 'g> QueryReservation<'m, 'g> {
pub(crate) const fn owner(&self) -> &'m QueryMemory<'g> {
self.memory
}
pub(crate) const fn bytes(&self) -> usize {
self.bytes
}
pub(crate) fn resize(&mut self, bytes: usize) -> Result<(), MemoryError> {
let remaining = self
.memory
.used
.get()
.checked_sub(self.bytes)
.ok_or(MemoryError::Limit)?;
let total = remaining.checked_add(bytes).ok_or(MemoryError::Limit)?;
if total > self.memory.limit {
return Err(MemoryError::Limit);
}
self.shared.resize(bytes)?;
self.bytes = bytes;
self.memory.used.set(total);
self.memory.peak.set(self.memory.peak.get().max(total));
Ok(())
}
}
impl Drop for QueryReservation<'_, '_> {
fn drop(&mut self) {
self.memory
.used
.set(self.memory.used.get().saturating_sub(self.bytes));
}
}
pub struct QueryArena<'m, 'g, T> {
values: Vec<T>,
charge: QueryReservation<'m, 'g>,
}
impl<'m, 'g, T> QueryArena<'m, 'g, T> {
pub fn new(memory: &'m QueryMemory<'g>, capacity: usize) -> Result<Self, MemoryError> {
if std::mem::size_of::<T>() == 0 {
return Err(MemoryError::Limit);
}
let heap = capacity
.checked_mul(std::mem::size_of::<T>())
.ok_or(MemoryError::Limit)?;
let mut charge = memory.reserve(
heap.checked_add(std::mem::size_of::<Self>())
.ok_or(MemoryError::Limit)?,
)?;
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(|_| MemoryError::Allocation)?;
let heap = values
.capacity()
.checked_mul(std::mem::size_of::<T>())
.ok_or(MemoryError::Limit)?;
charge.resize(
heap.checked_add(std::mem::size_of::<Self>())
.ok_or(MemoryError::Limit)?,
)?;
Ok(Self { values, charge })
}
pub fn capacity(&self) -> usize {
self.values.capacity()
}
pub fn heap_bytes(&self) -> usize {
self.values.capacity() * std::mem::size_of::<T>()
}
pub const fn reserved_bytes(&self) -> usize {
self.charge.bytes
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
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<(), MemoryError> {
if self.values.len() == self.values.capacity() {
return Err(MemoryError::Limit);
}
self.values.push(value);
Ok(())
}
pub fn clear(&mut self) {
self.values.clear();
}
pub(crate) fn extend_copy(&mut self, values: &[T]) -> Result<(), MemoryError>
where
T: Copy,
{
if self
.values
.len()
.checked_add(values.len())
.is_none_or(|n| n > self.values.capacity())
{
return Err(MemoryError::Limit);
}
self.values.extend_from_slice(values);
Ok(())
}
pub(crate) fn truncate(&mut self, length: usize) {
self.values.truncate(length);
}
pub(super) fn detach_owned(self) -> Vec<T>
where
T: super::completed::OwnedElement,
{
let Self { values, charge } = self;
drop(charge);
values
}
}
pub struct QueryExternalReservation<'m, 'g> {
charge: QueryReservation<'m, 'g>,
}
impl<'g> QueryMemory<'g> {
pub fn reserve_external_capacity(
&self,
) -> Result<QueryExternalReservation<'_, 'g>, MemoryError> {
Ok(QueryExternalReservation {
charge: self.reserve(std::mem::size_of::<QueryExternalReservation<'_, '_>>())?,
})
}
}
pub struct QueryRetainedReservation<'m, 'g> {
charge: QueryExternalReservation<'m, 'g>,
release: usize,
}
impl QueryRetainedReservation<'_, '_> {
pub fn reserved_bytes(&self) -> usize {
self.charge.charge.bytes
}
pub fn detach(self) -> GraphReservation {
let Self { charge, release } = self;
let charge = std::mem::ManuallyDrop::new(charge.charge);
charge
.memory
.used
.set(charge.memory.used.get().saturating_sub(charge.bytes));
let mut shared = unsafe { std::ptr::read(&charge.shared) };
shared.release(release);
shared
}
}
impl<'m, 'g> QueryExternalReservation<'m, 'g> {
pub fn retain(self, bytes: usize) -> Result<QueryRetainedReservation<'m, 'g>, MemoryError> {
let release = self
.charge
.bytes
.checked_sub(bytes)
.ok_or(MemoryError::Limit)?;
Ok(QueryRetainedReservation {
charge: self,
release,
})
}
pub fn reserve_additional(&mut self, bytes: usize) -> Result<(), MemoryError> {
self.charge.resize(
self.charge
.bytes
.checked_add(bytes)
.ok_or(MemoryError::Limit)?,
)
}
pub fn release_capacity(&mut self, bytes: usize) -> Result<(), MemoryError> {
let retained = self.bytes().checked_sub(bytes).ok_or(MemoryError::Limit)?;
self.charge.resize(retained + std::mem::size_of::<Self>())
}
pub fn bytes(&self) -> usize {
self.charge
.bytes
.saturating_sub(std::mem::size_of::<Self>())
}
}