use std::collections::{BTreeMap, BTreeSet};
use crate::{Budget, BudgetError, Resource};
#[derive(Debug)]
#[must_use = "dropping an active reservation releases its capacity"]
pub struct Reservation {
budget: Budget,
resource: Resource,
amount: u64,
active: bool,
}
impl Reservation {
pub(crate) fn new(budget: Budget, resource: Resource, amount: u64) -> Self {
Self {
budget,
resource,
amount,
active: amount != 0,
}
}
#[must_use]
pub fn resource(&self) -> &Resource {
&self.resource
}
#[must_use]
pub fn amount(&self) -> u64 {
self.amount
}
pub fn commit(mut self, actual: u64) -> Result<(), BudgetError> {
if self.amount == 0 {
if actual == 0 {
return Ok(());
}
return Err(BudgetError::ReservationExceeded {
resource: self.resource.clone(),
reserved: 0,
actual,
unaccounted: actual,
});
}
let reserved = BTreeMap::from([(self.resource.clone(), self.amount)]);
let accounted = BTreeMap::from([(self.resource.clone(), actual.min(self.amount))]);
self.budget.commit_reserved(&reserved, &accounted);
self.active = false;
self.budget
.trace_event("commit", &self.resource, actual.min(self.amount));
if actual > self.amount {
return Err(BudgetError::ReservationExceeded {
resource: self.resource.clone(),
reserved: self.amount,
actual,
unaccounted: actual - self.amount,
});
}
Ok(())
}
pub fn release(mut self) {
if self.active {
let reserved = BTreeMap::from([(self.resource.clone(), self.amount)]);
self.budget.release_reserved(&reserved);
self.active = false;
self.budget
.trace_event("release", &self.resource, self.amount);
}
}
}
impl Drop for Reservation {
fn drop(&mut self) {
if self.active {
let reserved = BTreeMap::from([(self.resource.clone(), self.amount)]);
self.budget.release_reserved(&reserved);
self.active = false;
self.budget
.trace_event("release", &self.resource, self.amount);
}
}
}
#[derive(Debug)]
#[must_use = "dropping an active reservation set releases its capacity"]
pub struct ReservationSet {
budget: Budget,
amounts: BTreeMap<Resource, u64>,
active: bool,
}
impl ReservationSet {
pub(crate) fn new(budget: Budget, amounts: BTreeMap<Resource, u64>) -> Self {
let active = !amounts.is_empty();
Self {
budget,
amounts,
active,
}
}
#[must_use]
pub fn amounts(&self) -> &BTreeMap<Resource, u64> {
&self.amounts
}
pub fn commit<'a>(
mut self,
actual: impl IntoIterator<Item = (&'a Resource, u64)>,
) -> Result<(), BudgetError> {
let mut actuals = BTreeMap::<Resource, u64>::new();
let mut overflows = BTreeSet::new();
for (resource, amount) in actual {
let current = actuals.entry(resource.clone()).or_default();
if let Some(sum) = current.checked_add(amount) {
*current = sum;
} else {
overflows.insert(resource.clone());
}
}
if let Some(resource) = actuals
.keys()
.find(|resource| !self.amounts.contains_key(*resource))
.cloned()
{
self.commit_all_inner();
return Err(BudgetError::UnknownReservationResource { resource });
}
if let Some(resource) = overflows.into_iter().next() {
self.commit_all_inner();
return Err(BudgetError::Overflow { resource });
}
let accounted = self
.amounts
.iter()
.map(|(resource, reserved)| {
let actual = actuals.get(resource).copied().unwrap_or(0);
(resource.clone(), actual.min(*reserved))
})
.collect();
self.budget.commit_reserved(&self.amounts, &accounted);
self.active = false;
for (resource, amount) in &accounted {
self.budget.trace_event("commit", resource, *amount);
}
for (resource, reserved) in &self.amounts {
let actual = actuals.get(resource).copied().unwrap_or(0);
if actual > *reserved {
return Err(BudgetError::ReservationExceeded {
resource: resource.clone(),
reserved: *reserved,
actual,
unaccounted: actual - reserved,
});
}
}
Ok(())
}
pub fn commit_all(mut self) {
self.commit_all_inner();
}
pub fn release(mut self) {
if self.active {
self.budget.release_reserved(&self.amounts);
self.active = false;
for (resource, amount) in &self.amounts {
self.budget.trace_event("release", resource, *amount);
}
}
}
fn commit_all_inner(&mut self) {
if self.active {
self.budget.commit_reserved(&self.amounts, &self.amounts);
self.active = false;
for (resource, amount) in &self.amounts {
self.budget.trace_event("commit", resource, *amount);
}
}
}
}
impl Drop for ReservationSet {
fn drop(&mut self) {
if self.active {
self.budget.release_reserved(&self.amounts);
self.active = false;
for (resource, amount) in &self.amounts {
self.budget.trace_event("release", resource, *amount);
}
}
}
}