use std::collections::HashMap;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use tokio::time::Instant;
use super::budget::BudgetRejection;
use super::budget::ResourceBudget;
use super::budget::ResourcePermit;
use super::limit::BudgetClass;
use crate::RuntimeContractViolation;
pub const DEFAULT_MAX_DYNAMIC_BUDGET_KEYS: usize = 4_096;
#[derive(Debug)]
pub enum DynamicKeyRegistrationFailure {
Closed,
Invalid(RuntimeContractViolation),
NameInUse {
generation: u64,
},
CapacityExhausted {
max_entries: usize,
},
}
#[derive(Debug)]
pub enum DynamicKeyAdmissionRejection {
Closed,
Budget(BudgetRejection),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DynamicKeyRetirement {
pub released: bool,
pub outstanding_reservations: usize,
pub outstanding_bytes: usize,
}
#[derive(Clone)]
pub struct DynamicBudgetKey {
inner: Arc<DynamicKeyInner>,
}
struct DynamicKeyInner {
registry: DynamicKeyRegistry,
budget: ResourceBudget,
name: Arc<str>,
generation: u64,
}
impl std::fmt::Debug for DynamicBudgetKey {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("DynamicBudgetKey")
.field("name", &self.inner.name)
.field("generation", &self.inner.generation)
.field("closed", &self.is_closed())
.finish_non_exhaustive()
}
}
impl DynamicBudgetKey {
#[must_use]
pub fn name(&self) -> &str {
&self.inner.name
}
#[must_use]
pub fn generation(&self) -> u64 {
self.inner.generation
}
#[must_use]
pub fn budget(&self) -> ResourceBudget {
self.inner.budget.clone()
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.inner.budget.is_closed()
}
pub fn close(&self) {
self.inner.budget.close_dynamic();
}
pub fn try_acquire(
&self,
bytes: usize,
class: BudgetClass,
) -> Result<ResourcePermit, DynamicKeyAdmissionRejection> {
self.inner.budget.try_acquire(bytes, class).map_err(|rejection| {
if rejection.is_closed() {
DynamicKeyAdmissionRejection::Closed
} else {
DynamicKeyAdmissionRejection::Budget(rejection)
}
})
}
pub async fn retire_until(&self, deadline: Instant) -> DynamicKeyRetirement {
self.close();
loop {
let snapshot = self.inner.budget.snapshot();
if snapshot.current_count == 0 {
let released = self.inner.registry.release(&self.inner.name, self.inner.generation);
return DynamicKeyRetirement {
released,
outstanding_reservations: 0,
outstanding_bytes: 0,
};
}
if Instant::now() >= deadline {
return DynamicKeyRetirement {
released: false,
outstanding_reservations: snapshot.current_count,
outstanding_bytes: snapshot.current_bytes,
};
}
let released = self.inner.budget.capacity_notify().notified();
tokio::pin!(released);
released.as_mut().enable();
let snapshot = self.inner.budget.snapshot();
if snapshot.current_count == 0 {
continue;
}
let expired = tokio::time::sleep_until(deadline);
tokio::pin!(expired);
tokio::select! {
biased;
() = &mut expired => {}
() = &mut released => {}
}
}
}
}
#[derive(Clone)]
pub(super) struct DynamicKeyRegistry {
inner: Arc<DynamicKeyRegistryInner>,
}
struct DynamicKeyRegistryInner {
entries: Mutex<HashMap<Arc<str>, u64>>,
next_generation: AtomicU64,
max_entries: usize,
}
impl std::fmt::Debug for DynamicKeyRegistry {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let entries = self
.inner
.entries
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len();
formatter
.debug_struct("DynamicKeyRegistry")
.field("entries", &entries)
.field("max_entries", &self.inner.max_entries)
.finish()
}
}
impl DynamicKeyRegistry {
pub(super) fn new() -> Self {
Self::with_max_entries(DEFAULT_MAX_DYNAMIC_BUDGET_KEYS)
}
pub(super) fn with_max_entries(max_entries: usize) -> Self {
Self {
inner: Arc::new(DynamicKeyRegistryInner {
entries: Mutex::new(HashMap::new()),
next_generation: AtomicU64::new(1),
max_entries,
}),
}
}
pub(super) fn register(
&self,
budget: ResourceBudget,
name: Arc<str>,
) -> Result<DynamicBudgetKey, DynamicKeyRegistrationFailure> {
let mut entries = self
.inner
.entries
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(generation) = entries.get(&name) {
return Err(DynamicKeyRegistrationFailure::NameInUse {
generation: *generation,
});
}
if entries.len() >= self.inner.max_entries {
return Err(DynamicKeyRegistrationFailure::CapacityExhausted {
max_entries: self.inner.max_entries,
});
}
let generation = self.inner.next_generation.fetch_add(1, Ordering::Relaxed);
entries.insert(Arc::clone(&name), generation);
drop(entries);
Ok(DynamicBudgetKey {
inner: Arc::new(DynamicKeyInner {
registry: self.clone(),
budget,
name,
generation,
}),
})
}
fn release(&self, name: &Arc<str>, generation: u64) -> bool {
let mut entries = self
.inner
.entries
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if entries.get(name) != Some(&generation) {
return false;
}
entries.remove(name);
true
}
}