use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use nodedb_types::{DatabaseId, TenantId};
use crate::engine::EngineId;
use crate::governor::GlobalCounter;
#[must_use = "dropping a ReservationToken immediately releases the reservation; bind it to a variable"]
pub struct ReservationToken {
pub(crate) global_counter: Arc<GlobalCounter>,
pub(crate) database_counter: Option<Arc<AtomicUsize>>,
pub(crate) tenant_counter: Option<Arc<AtomicUsize>>,
pub(crate) engine_counter: Option<Arc<AtomicUsize>>,
pub(crate) size: usize,
db: DatabaseId,
tenant: TenantId,
engine: EngineId,
}
pub(crate) struct ReservationParams {
pub global_counter: Arc<GlobalCounter>,
pub database_counter: Option<Arc<AtomicUsize>>,
pub tenant_counter: Option<Arc<AtomicUsize>>,
pub engine_counter: Option<Arc<AtomicUsize>>,
pub size: usize,
pub db: DatabaseId,
pub tenant: TenantId,
pub engine: EngineId,
}
impl ReservationToken {
pub(crate) fn new(params: ReservationParams) -> Self {
Self {
global_counter: params.global_counter,
database_counter: params.database_counter,
tenant_counter: params.tenant_counter,
engine_counter: params.engine_counter,
size: params.size,
db: params.db,
tenant: params.tenant,
engine: params.engine,
}
}
pub fn size(&self) -> usize {
self.size
}
pub fn database_id(&self) -> DatabaseId {
self.db
}
pub fn tenant_id(&self) -> TenantId {
self.tenant
}
pub fn engine(&self) -> EngineId {
self.engine
}
}
impl Drop for ReservationToken {
fn drop(&mut self) {
let size = self.size;
if size == 0 {
return;
}
if let Some(ref counter) = self.engine_counter {
counter.fetch_sub(size, Ordering::Relaxed);
}
if let Some(ref counter) = self.tenant_counter {
counter.fetch_sub(size, Ordering::Relaxed);
}
if let Some(ref counter) = self.database_counter {
counter.fetch_sub(size, Ordering::Relaxed);
}
self.global_counter
.allocated
.fetch_sub(size, Ordering::Relaxed);
}
}
impl std::fmt::Debug for ReservationToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReservationToken")
.field("size", &self.size)
.field("db", &self.db)
.field("tenant", &self.tenant)
.field("engine", &self.engine)
.finish()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use nodedb_types::{DatabaseId, TenantId};
use super::{ReservationParams, ReservationToken};
use crate::engine::EngineId;
use crate::governor::GlobalCounter;
fn make_counter(val: usize) -> Arc<AtomicUsize> {
Arc::new(AtomicUsize::new(val))
}
fn make_global(val: usize) -> Arc<GlobalCounter> {
Arc::new(GlobalCounter {
allocated: AtomicUsize::new(val),
ceiling: 1024 * 1024,
})
}
#[test]
fn drop_releases_all_four_levels() {
let global = make_global(100);
let db_ctr = make_counter(100);
let tenant_ctr = make_counter(100);
let engine_ctr = make_counter(100);
let token = ReservationToken::new(ReservationParams {
global_counter: Arc::clone(&global),
database_counter: Some(Arc::clone(&db_ctr)),
tenant_counter: Some(Arc::clone(&tenant_ctr)),
engine_counter: Some(Arc::clone(&engine_ctr)),
size: 100,
db: DatabaseId::DEFAULT,
tenant: TenantId::new(1),
engine: EngineId::Vector,
});
assert_eq!(
global.allocated.load(std::sync::atomic::Ordering::Relaxed),
100
);
drop(token);
assert_eq!(
global.allocated.load(std::sync::atomic::Ordering::Relaxed),
0
);
assert_eq!(db_ctr.load(std::sync::atomic::Ordering::Relaxed), 0);
assert_eq!(tenant_ctr.load(std::sync::atomic::Ordering::Relaxed), 0);
assert_eq!(engine_ctr.load(std::sync::atomic::Ordering::Relaxed), 0);
}
#[test]
fn drop_with_no_scoped_counters_releases_global() {
let global = make_global(200);
let token = ReservationToken::new(ReservationParams {
global_counter: Arc::clone(&global),
database_counter: None,
tenant_counter: None,
engine_counter: None,
size: 200,
db: DatabaseId::DEFAULT,
tenant: TenantId::new(1),
engine: EngineId::Query,
});
drop(token);
assert_eq!(
global.allocated.load(std::sync::atomic::Ordering::Relaxed),
0
);
}
#[test]
fn zero_size_drop_is_noop() {
let global = make_global(0);
let token = ReservationToken::new(ReservationParams {
global_counter: Arc::clone(&global),
database_counter: None,
tenant_counter: None,
engine_counter: None,
size: 0,
db: DatabaseId::DEFAULT,
tenant: TenantId::new(1),
engine: EngineId::Query,
});
drop(token);
assert_eq!(
global.allocated.load(std::sync::atomic::Ordering::Relaxed),
0
);
}
}