use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use linkrs_core::error::QueryError;
use super::chunk::DataChunk;
#[derive(Debug, Clone)]
pub enum MemoryPoolError {
ExceededLimit {
requested: usize,
available: usize,
pool_name: &'static str,
},
ShutDown(&'static str),
}
impl std::fmt::Display for MemoryPoolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ExceededLimit {
requested,
available,
pool_name,
} => {
write!(
f,
"{}: requested {} bytes, {} bytes available",
pool_name, requested, available
)
}
Self::ShutDown(name) => write!(f, "{}: pool is shut down", name),
}
}
}
impl std::error::Error for MemoryPoolError {}
impl From<MemoryPoolError> for QueryError {
fn from(e: MemoryPoolError) -> Self {
QueryError::execution(e.to_string())
}
}
#[derive(Debug)]
struct PoolInner {
max_bytes: usize,
used: AtomicUsize,
shut_down: std::sync::atomic::AtomicBool,
}
impl PoolInner {
fn new(max_bytes: usize) -> Self {
Self {
max_bytes,
used: AtomicUsize::new(0),
shut_down: std::sync::atomic::AtomicBool::new(false),
}
}
fn try_reserve(&self, bytes: usize) -> Result<(), MemoryPoolError> {
if self.shut_down.load(Ordering::Relaxed) {
return Err(MemoryPoolError::ShutDown("pool"));
}
if self.max_bytes == 0 {
self.used.fetch_add(bytes, Ordering::Relaxed);
return Ok(());
}
loop {
let current = self.used.load(Ordering::Relaxed);
let new_total =
current
.checked_add(bytes)
.ok_or_else(|| MemoryPoolError::ExceededLimit {
requested: bytes,
available: self.max_bytes.saturating_sub(current),
pool_name: "pool (overflow)",
})?;
if new_total > self.max_bytes {
return Err(MemoryPoolError::ExceededLimit {
requested: bytes,
available: self.max_bytes.saturating_sub(current),
pool_name: "pool",
});
}
if self
.used
.compare_exchange_weak(current, new_total, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
return Ok(());
}
}
}
fn release(&self, bytes: usize) {
self.used.fetch_sub(bytes, Ordering::Relaxed);
}
fn current(&self) -> usize {
self.used.load(Ordering::Relaxed)
}
fn shut_down(&self) {
self.shut_down.store(true, Ordering::Relaxed);
}
}
#[derive(Debug)]
pub struct MemoryPoolReservation {
inner: Option<Arc<PoolInner>>,
bytes: usize,
}
impl MemoryPoolReservation {
fn new(inner: Arc<PoolInner>, bytes: usize) -> Self {
Self {
inner: Some(inner),
bytes,
}
}
pub fn forget(mut self) {
self.bytes = 0;
self.inner = None;
}
pub fn bytes(&self) -> usize {
self.bytes
}
}
impl Drop for MemoryPoolReservation {
fn drop(&mut self) {
if let Some(ref inner) = self.inner {
if self.bytes > 0 {
inner.release(self.bytes);
}
}
}
}
impl Clone for MemoryPoolReservation {
fn clone(&self) -> Self {
if let Some(ref inner) = self.inner {
Self {
inner: Some(Arc::clone(inner)),
bytes: self.bytes,
}
} else {
Self {
inner: None,
bytes: 0,
}
}
}
}
#[derive(Debug, Clone)]
pub struct PoolHandle {
inner: Arc<PoolInner>,
}
impl PoolHandle {
fn new(max_bytes: usize) -> Self {
Self {
inner: Arc::new(PoolInner::new(max_bytes)),
}
}
pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
self.inner.try_reserve(bytes)?;
Ok(MemoryPoolReservation::new(Arc::clone(&self.inner), bytes))
}
pub fn current(&self) -> usize {
self.inner.current()
}
pub fn max(&self) -> usize {
self.inner.max_bytes
}
pub fn shut_down(&self) {
self.inner.shut_down();
}
}
#[derive(Debug, Clone)]
pub struct DatabaseMemoryPool {
handle: PoolHandle,
max_query_bytes: usize,
}
impl DatabaseMemoryPool {
pub fn new(max_bytes: usize, max_query_bytes: usize) -> Self {
Self {
handle: PoolHandle::new(max_bytes),
max_query_bytes,
}
}
pub fn default_pool() -> Self {
Self::new(8 * 1024 * 1024 * 1024, 512 * 1024 * 1024)
}
pub fn unlimited() -> Self {
Self::new(0, usize::MAX)
}
pub fn new_query_pool(&self, requested_bytes: usize) -> Result<QueryPool, MemoryPoolError> {
let effective = requested_bytes.min(self.max_query_bytes);
let reservation = self.handle.reserve(effective)?;
Ok(QueryPool {
handle: PoolHandle::new(effective),
_reservation: reservation,
})
}
pub fn current(&self) -> usize {
self.handle.current()
}
pub fn max(&self) -> usize {
self.handle.max()
}
pub fn shut_down(&self) {
self.handle.shut_down();
}
pub fn handle(&self) -> PoolHandle {
self.handle.clone()
}
}
#[derive(Debug, Clone)]
pub struct QueryPool {
handle: PoolHandle,
_reservation: MemoryPoolReservation,
}
impl QueryPool {
pub fn new_fragment_pool(
&self,
requested_bytes: usize,
) -> Result<FragmentPool, MemoryPoolError> {
let reservation = self.handle.reserve(requested_bytes)?;
Ok(FragmentPool {
handle: PoolHandle::new(requested_bytes),
_reservation: reservation,
})
}
pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
self.handle.reserve(bytes)
}
pub fn current(&self) -> usize {
self.handle.current()
}
pub fn max(&self) -> usize {
self.handle.max()
}
}
#[derive(Debug, Clone)]
pub struct FragmentPool {
handle: PoolHandle,
_reservation: MemoryPoolReservation,
}
impl FragmentPool {
pub fn new_operator_pool(
&self,
requested_bytes: usize,
) -> Result<OperatorPool, MemoryPoolError> {
let reservation = self.handle.reserve(requested_bytes)?;
Ok(OperatorPool {
handle: PoolHandle::new(requested_bytes),
_reservation: reservation,
})
}
pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
self.handle.reserve(bytes)
}
pub fn current(&self) -> usize {
self.handle.current()
}
}
#[derive(Debug, Clone)]
pub struct OperatorPool {
handle: PoolHandle,
_reservation: MemoryPoolReservation,
}
impl OperatorPool {
pub fn new_task_pool(&self, requested_bytes: usize) -> Result<TaskPool, MemoryPoolError> {
let reservation = self.handle.reserve(requested_bytes)?;
Ok(TaskPool {
handle: PoolHandle::new(requested_bytes),
_reservation: reservation,
})
}
pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
self.handle.reserve(bytes)
}
pub fn current(&self) -> usize {
self.handle.current()
}
}
#[derive(Debug, Clone)]
pub struct TaskPool {
handle: PoolHandle,
_reservation: MemoryPoolReservation,
}
impl TaskPool {
pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
self.handle.reserve(bytes)
}
pub fn current(&self) -> usize {
self.handle.current()
}
}
#[derive(Debug)]
pub struct PooledChunk {
pub chunk: DataChunk,
pub reservation: MemoryPoolReservation,
}
impl PooledChunk {
pub fn new(chunk: DataChunk, reservation: MemoryPoolReservation) -> Self {
Self { chunk, reservation }
}
pub fn into_parts(self) -> (DataChunk, MemoryPoolReservation) {
(self.chunk, self.reservation)
}
}
impl PoolHandle {
pub fn copy_chunk(&self, chunk: &DataChunk) -> Result<PooledChunk, QueryError> {
let estimated = chunk.estimated_bytes();
let reservation = self
.reserve(estimated)
.map_err(|e| QueryError::execution(e.to_string()))?;
let copied = DataChunk {
rows: chunk.rows.clone(),
typed_columns: None,
columnar_build_deferred: false,
selection: chunk.selection.clone(),
multiplicity: chunk.multiplicity,
schema: std::sync::Arc::clone(&chunk.schema),
layout: std::sync::Arc::clone(&chunk.layout),
memory_reservation: None,
columnar_stats: chunk.columnar_stats.clone(),
};
Ok(PooledChunk {
chunk: copied,
reservation,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_database_pool_admission() {
let db = DatabaseMemoryPool::new(1024, 400);
let _qp = db
.new_query_pool(500)
.expect("first query admitted (capped to 400)");
let _qp2 = db
.new_query_pool(700)
.expect("second query admitted (capped to 400, 800 <= 1024)");
let result = db.new_query_pool(400);
assert!(
result.is_err(),
"third query should exceed global limit (1200 > 1024)"
);
}
#[test]
fn test_query_pool_reserve_release() {
let db = DatabaseMemoryPool::unlimited();
let qp = db.new_query_pool(1024).expect("query pool");
let reservation = qp.reserve(128).expect("reserve");
assert_eq!(qp.current(), 128);
drop(reservation);
assert_eq!(qp.current(), 0);
}
#[test]
fn test_hierarchical_reservation() {
let db = DatabaseMemoryPool::new(4096, 2048);
let qp = db.new_query_pool(2048).expect("query");
let fp = qp.new_fragment_pool(1024).expect("fragment");
let op = fp.new_operator_pool(512).expect("operator");
let _tp = op.new_task_pool(256).expect("task");
assert_eq!(op.current(), 256);
assert_eq!(fp.current(), 512);
assert_eq!(qp.current(), 1024);
assert!(db.current() >= 2048);
}
#[test]
fn test_unlimited_pool() {
let db = DatabaseMemoryPool::unlimited();
let qp = db.new_query_pool(usize::MAX).expect("unlimited query");
let r = qp.reserve(1_000_000).expect("large reserve");
assert!(qp.current() >= 1_000_000);
drop(r);
}
#[test]
fn test_shut_down_rejects() {
let db = DatabaseMemoryPool::new(1024, 512);
db.shut_down();
let result = db.new_query_pool(64);
assert!(result.is_err());
match result {
Err(MemoryPoolError::ShutDown(_)) => {} _ => panic!("expected ShutDown error"),
}
}
#[test]
fn test_reservation_forget() {
let db = DatabaseMemoryPool::unlimited();
let qp = db.new_query_pool(1024).expect("query");
let r = qp.reserve(256).expect("reserve");
r.forget();
assert!(qp.current() >= 256);
}
}