use std::collections::HashMap;
use std::collections::hash_map::Entry;
use crate::contract::{RingContract, Violation};
use crate::{Completion, Token};
pub struct Pending<T: Send + 'static, X = ()> {
entries: HashMap<usize, (Token<T>, X)>,
contract: Option<RingContract>,
}
impl<T: Send + 'static, X> Pending<T, X> {
#[must_use]
pub fn new() -> Self {
Self {
entries: HashMap::new(),
contract: None,
}
}
#[must_use]
pub fn checked() -> Self {
Self {
entries: HashMap::new(),
contract: Some(RingContract::new()),
}
}
pub fn push(&mut self, token: Token<T>, extra: X) {
let user_data = token.id();
match self.entries.entry(user_data) {
Entry::Occupied(_) => {
panic!("user_data {user_data:#x} is already pending; identities are minted unique")
}
Entry::Vacant(slot) => {
slot.insert((token, extra));
}
}
if let Some(contract) = &mut self.contract {
contract.observe_push(user_data);
}
}
pub fn claim(&mut self, completion: &Completion) -> Option<(T, X)> {
let user_data = completion.user_data();
let (token, extra) = self.entries.remove(&user_data)?;
if let Some(contract) = &mut self.contract {
contract.observe_completion(user_data);
}
match token.claim_if(completion) {
Ok(payload) => {
if let Some(contract) = &mut self.contract {
contract.observe_claim(user_data);
}
Some((payload, extra))
}
Err(token) => {
self.entries.insert(user_data, (token, extra));
None
}
}
}
pub fn abandon(&mut self, user_data: usize) -> bool {
let removed = self.entries.remove(&user_data).is_some();
if removed && let Some(contract) = &mut self.contract {
contract.observe_deliberate_leak(user_data);
}
removed
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn contract(&self) -> Option<&RingContract> {
self.contract.as_ref()
}
#[must_use]
pub fn finish(mut self) -> Vec<Violation> {
let mut violations = match &self.contract {
Some(contract) => contract.check_quiescent(),
None => Vec::new(),
};
for user_data in self.entries.keys() {
violations.push(Violation::LeakedToken {
user_data: *user_data,
});
}
self.entries.clear();
self.contract = None;
violations
}
}
impl<T: Send + 'static, X> Default for Pending<T, X> {
fn default() -> Self {
Self::new()
}
}
impl<T: Send + 'static, X> std::fmt::Debug for Pending<T, X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ids: Vec<usize> = self.entries.keys().copied().collect();
ids.sort_unstable();
f.debug_struct("Pending")
.field("outstanding", &self.entries.len())
.field("user_data", &ids)
.field("checked", &self.contract.is_some())
.finish()
}
}
impl<T: Send + 'static, X> Drop for Pending<T, X> {
fn drop(&mut self) {
if self.entries.is_empty() || std::thread::panicking() {
return;
}
let mut ids: Vec<usize> = self.entries.keys().copied().collect();
ids.sort_unstable();
panic!(
"{} token(s) dropped unclaimed, leaking their buffers: {ids:#x?}. \
Claim them, or say so with `abandon` or `finish`.",
ids.len()
);
}
}
#[cfg(test)]
mod tests;