use std::fmt::Debug;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
pub trait MemoryReservation: Debug + Send + Sync {
fn size(&self) -> usize;
fn resize(&mut self, new_size: usize);
}
pub trait MemoryPool: Debug + Send + Sync {
fn reserve(&self, size: usize) -> Box<dyn MemoryReservation>;
fn available(&self) -> isize;
fn used(&self) -> usize;
fn capacity(&self) -> usize;
}
#[derive(Debug, Default)]
pub struct TrackingMemoryPool(Arc<AtomicUsize>);
impl TrackingMemoryPool {
pub fn allocated(&self) -> usize {
self.0.load(Ordering::Relaxed)
}
}
impl MemoryPool for TrackingMemoryPool {
fn reserve(&self, size: usize) -> Box<dyn MemoryReservation> {
self.0.fetch_add(size, Ordering::Relaxed);
Box::new(Tracker {
size,
shared: Arc::clone(&self.0),
})
}
fn available(&self) -> isize {
isize::MAX - self.used() as isize
}
fn used(&self) -> usize {
self.0.load(Ordering::Relaxed)
}
fn capacity(&self) -> usize {
usize::MAX
}
}
#[derive(Debug)]
struct Tracker {
size: usize,
shared: Arc<AtomicUsize>,
}
impl Drop for Tracker {
fn drop(&mut self) {
self.shared.fetch_sub(self.size, Ordering::Relaxed);
}
}
impl MemoryReservation for Tracker {
fn size(&self) -> usize {
self.size
}
fn resize(&mut self, new: usize) {
match self.size < new {
true => self.shared.fetch_add(new - self.size, Ordering::Relaxed),
false => self.shared.fetch_sub(self.size - new, Ordering::Relaxed),
};
self.size = new;
}
}
pub(crate) fn lock_reservation(
reservation: &Mutex<Option<Box<dyn MemoryReservation>>>,
) -> MutexGuard<'_, Option<Box<dyn MemoryReservation>>> {
reservation.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Debug, Default)]
pub(crate) struct TrackedReservation {
reservation: Mutex<Option<Box<dyn MemoryReservation>>>,
}
impl TrackedReservation {
pub fn claim(&self, pool: &dyn MemoryPool, capacity: usize) {
let mut guard = lock_reservation(&self.reservation);
drop(guard.take());
*guard = Some(pool.reserve(capacity))
}
pub fn resize(&self, new_size: usize) {
if let Some(reservation) = lock_reservation(&self.reservation).as_mut() {
reservation.resize(new_size);
}
}
pub fn take(&self) -> Self {
let reservation = lock_reservation(&self.reservation).take();
Self {
reservation: Mutex::new(reservation),
}
}
pub fn replace(&self, other: Self) {
let reservation = other
.reservation
.into_inner()
.unwrap_or_else(PoisonError::into_inner);
let mut guard = lock_reservation(&self.reservation);
drop(guard.take());
*guard = reservation;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracking_memory_pool() {
let pool = TrackingMemoryPool::default();
let reservation = pool.reserve(512);
assert_eq!(reservation.size(), 512);
assert_eq!(pool.used(), 512);
assert_eq!(pool.available(), isize::MAX - 512);
let reservation2 = pool.reserve(256);
assert_eq!(reservation2.size(), 256);
assert_eq!(pool.used(), 768);
assert_eq!(pool.available(), isize::MAX - 768);
let mut reservation_mut = reservation;
reservation_mut.resize(600);
assert_eq!(reservation_mut.size(), 600);
assert_eq!(pool.used(), 856);
reservation_mut.resize(400);
assert_eq!(reservation_mut.size(), 400);
assert_eq!(pool.used(), 656);
drop(reservation_mut);
assert_eq!(pool.used(), 256);
drop(reservation2);
assert_eq!(pool.used(), 0);
}
#[derive(Debug, Default)]
struct PeakPool {
inner: TrackingMemoryPool,
peak: AtomicUsize,
}
impl MemoryPool for PeakPool {
fn reserve(&self, size: usize) -> Box<dyn MemoryReservation> {
let reservation = self.inner.reserve(size);
self.peak.fetch_max(self.inner.used(), Ordering::Relaxed);
reservation
}
fn available(&self) -> isize {
self.inner.available()
}
fn used(&self) -> usize {
self.inner.used()
}
fn capacity(&self) -> usize {
self.inner.capacity()
}
}
#[test]
fn test_claim_reclaims_before_reserving() {
let pool = PeakPool::default();
let reservation = TrackedReservation::default();
reservation.claim(&pool, 512);
assert_eq!(pool.used(), 512);
reservation.claim(&pool, 512);
assert_eq!(pool.used(), 512);
assert_eq!(
pool.peak.load(Ordering::Relaxed),
512,
"claim double-counted memory while reclaiming"
);
}
}