use crate::error::Result;
use std::sync::Arc;
mod pool;
pub mod proxy;
pub use pool::*;
pub trait MemoryPool: Send + Sync + std::fmt::Debug {
fn register(&self, _consumer: &MemoryConsumer) {}
fn unregister(&self, _consumer: &MemoryConsumer) {}
fn grow(&self, reservation: &MemoryReservation, additional: usize);
fn shrink(&self, reservation: &MemoryReservation, shrink: usize);
fn try_grow(&self, reservation: &MemoryReservation, additional: usize) -> Result<()>;
fn reserved(&self) -> usize;
}
#[derive(Debug)]
pub struct MemoryConsumer {
name: String,
can_spill: bool,
}
impl MemoryConsumer {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
can_spill: false,
}
}
pub fn with_can_spill(self, can_spill: bool) -> Self {
Self { can_spill, ..self }
}
pub fn can_spill(&self) -> bool {
self.can_spill
}
pub fn name(&self) -> &str {
&self.name
}
pub fn register(self, pool: &Arc<dyn MemoryPool>) -> MemoryReservation {
pool.register(&self);
MemoryReservation {
consumer: self,
size: 0,
policy: Arc::clone(pool),
}
}
}
#[derive(Debug)]
pub struct MemoryReservation {
consumer: MemoryConsumer,
size: usize,
policy: Arc<dyn MemoryPool>,
}
impl MemoryReservation {
pub fn size(&self) -> usize {
self.size
}
pub fn free(&mut self) -> usize {
let size = self.size;
if size != 0 {
self.shrink(size)
}
size
}
pub fn shrink(&mut self, capacity: usize) {
let new_size = self.size.checked_sub(capacity).unwrap();
self.policy.shrink(self, capacity);
self.size = new_size
}
pub fn resize(&mut self, capacity: usize) {
use std::cmp::Ordering;
match capacity.cmp(&self.size) {
Ordering::Greater => self.grow(capacity - self.size),
Ordering::Less => self.shrink(self.size - capacity),
_ => {}
}
}
pub fn grow(&mut self, capacity: usize) {
self.policy.grow(self, capacity);
self.size += capacity;
}
pub fn try_grow(&mut self, capacity: usize) -> Result<()> {
self.policy.try_grow(self, capacity)?;
self.size += capacity;
Ok(())
}
}
impl Drop for MemoryReservation {
fn drop(&mut self) {
self.free();
self.policy.unregister(&self.consumer);
}
}
const TB: u64 = 1 << 40;
const GB: u64 = 1 << 30;
const MB: u64 = 1 << 20;
const KB: u64 = 1 << 10;
pub fn human_readable_size(size: usize) -> String {
let size = size as u64;
let (value, unit) = {
if size >= 2 * TB {
(size as f64 / TB as f64, "TB")
} else if size >= 2 * GB {
(size as f64 / GB as f64, "GB")
} else if size >= 2 * MB {
(size as f64 / MB as f64, "MB")
} else if size >= 2 * KB {
(size as f64 / KB as f64, "KB")
} else {
(size as f64, "B")
}
};
format!("{value:.1} {unit}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_pool_underflow() {
let pool = Arc::new(GreedyMemoryPool::new(50)) as _;
let mut a1 = MemoryConsumer::new("a1").register(&pool);
assert_eq!(pool.reserved(), 0);
a1.grow(100);
assert_eq!(pool.reserved(), 100);
assert_eq!(a1.free(), 100);
assert_eq!(pool.reserved(), 0);
a1.try_grow(100).unwrap_err();
assert_eq!(pool.reserved(), 0);
a1.try_grow(30).unwrap();
assert_eq!(pool.reserved(), 30);
let mut a2 = MemoryConsumer::new("a2").register(&pool);
a2.try_grow(25).unwrap_err();
assert_eq!(pool.reserved(), 30);
drop(a1);
assert_eq!(pool.reserved(), 0);
a2.try_grow(25).unwrap();
assert_eq!(pool.reserved(), 25);
}
}