use std::cell::RefCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[derive(Debug)]
pub struct GasMeter {
remaining: AtomicU64,
}
impl GasMeter {
pub fn new(credits: u64) -> Arc<Self> {
Arc::new(Self {
remaining: AtomicU64::new(credits),
})
}
pub fn remaining(&self) -> u64 {
self.remaining.load(Ordering::Relaxed)
}
pub fn charge(&self, cost: u64) -> bool {
self.remaining
.try_update(Ordering::Relaxed, Ordering::Relaxed, |remaining| {
remaining.checked_sub(cost)
})
.is_ok()
}
}
thread_local! {
static ACTIVE: RefCell<Vec<Arc<GasMeter>>> = const { RefCell::new(Vec::new()) };
static EXHAUSTED: RefCell<Vec<bool>> = const { RefCell::new(Vec::new()) };
static INTERRUPTS: RefCell<Vec<Arc<AtomicBool>>> = const { RefCell::new(Vec::new()) };
}
#[must_use = "dropping InterruptGuard immediately uninstalls the interrupt flag"]
pub struct InterruptGuard;
impl InterruptGuard {
pub fn install(flag: Arc<AtomicBool>) -> Self {
INTERRUPTS.with(|flags| flags.borrow_mut().push(flag));
Self
}
}
impl Drop for InterruptGuard {
fn drop(&mut self) {
INTERRUPTS.with(|flags| {
flags.borrow_mut().pop();
});
}
}
pub fn interrupt_requested() -> bool {
INTERRUPTS.with(|flags| {
flags
.borrow()
.iter()
.any(|flag| flag.load(Ordering::Relaxed))
})
}
pub fn active_interrupts() -> Vec<Arc<AtomicBool>> {
INTERRUPTS.with(|flags| flags.borrow().clone())
}
#[must_use = "dropping InterruptScope immediately restores the previous interrupt flags"]
pub struct InterruptScope {
outer: Vec<Arc<AtomicBool>>,
}
impl InterruptScope {
pub fn enter(flags: &[Arc<AtomicBool>]) -> Self {
let outer = INTERRUPTS.with(|installed| installed.replace(flags.to_vec()));
Self { outer }
}
}
impl Drop for InterruptScope {
fn drop(&mut self) {
INTERRUPTS.with(|installed| {
installed.replace(std::mem::take(&mut self.outer));
});
}
}
#[must_use = "dropping GasGuard immediately uninstalls the active gas meter"]
pub struct GasGuard;
impl GasGuard {
pub fn install(meter: Arc<GasMeter>) -> Self {
ACTIVE.with(|active| active.borrow_mut().push(meter));
EXHAUSTED.with(|exhausted| exhausted.borrow_mut().push(false));
Self
}
}
impl Drop for GasGuard {
fn drop(&mut self) {
ACTIVE.with(|active| {
let mut active = active.borrow_mut();
active.pop();
});
EXHAUSTED.with(|exhausted| {
exhausted.borrow_mut().pop();
});
}
}
pub fn charge(cost: u64) -> bool {
if interrupt_requested() || is_exhausted() {
return false;
}
let charged = ACTIVE.with(|active| {
let active = active.borrow();
if active.is_empty() {
return true;
}
if active.iter().any(|meter| meter.remaining() < cost) {
return false;
}
active.iter().all(|meter| meter.charge(cost))
});
if !charged {
ACTIVE.with(|active| {
let active = active.borrow();
EXHAUSTED.with(|exhausted| {
for (index, meter) in active.iter().enumerate() {
if meter.remaining() < cost {
exhausted.borrow_mut()[index] = true;
}
}
});
});
}
charged
}
pub fn is_exhausted() -> bool {
interrupt_requested()
|| EXHAUSTED.with(|exhausted| exhausted.borrow().iter().any(|value| *value))
}
pub fn active_meters() -> Vec<Arc<GasMeter>> {
ACTIVE.with(|active| active.borrow().clone())
}
pub fn install_meters(meters: &[Arc<GasMeter>]) -> Vec<GasGuard> {
meters.iter().cloned().map(GasGuard::install).collect()
}
pub fn take_exhausted() -> bool {
EXHAUSTED.with(|exhausted| {
let mut exhausted = exhausted.borrow_mut();
let was_exhausted = exhausted.iter().any(|value| *value);
exhausted.fill(false);
was_exhausted
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scoped_meter_charges_without_partial_consumption() {
let meter = GasMeter::new(3);
let _guard = GasGuard::install(meter.clone());
assert!(charge(2));
assert!(!charge(2));
assert_eq!(meter.remaining(), 1);
}
#[test]
fn nested_meters_charge_outer_budget() {
let outer = GasMeter::new(3);
let _outer_guard = GasGuard::install(outer.clone());
let inner = GasMeter::new(2);
let _inner_guard = GasGuard::install(inner.clone());
assert!(charge(2));
assert_eq!(outer.remaining(), 1);
assert_eq!(inner.remaining(), 0);
assert!(!charge(1));
}
#[test]
fn inner_exhaustion_does_not_poison_healthy_outer_scope() {
let outer = GasMeter::new(10);
let _outer_guard = GasGuard::install(outer.clone());
{
let inner = GasMeter::new(0);
let _inner_guard = GasGuard::install(inner);
assert!(!charge(1));
assert!(is_exhausted());
}
assert!(!is_exhausted());
assert!(charge(1));
assert_eq!(outer.remaining(), 9);
}
#[test]
fn interrupt_flag_fails_charges_until_uninstalled() {
let flag = Arc::new(AtomicBool::new(false));
{
let _guard = InterruptGuard::install(flag.clone());
assert!(charge(1));
flag.store(true, Ordering::Relaxed);
assert!(!charge(1));
assert!(is_exhausted());
assert!(interrupt_requested());
}
assert!(!interrupt_requested());
assert!(!is_exhausted());
assert!(charge(1));
}
#[test]
fn interrupt_scope_replaces_the_installed_flags_and_restores_them() {
let driver = Arc::new(AtomicBool::new(true));
let _guard = InterruptGuard::install(driver.clone());
assert!(!charge(1));
{
let _scope = InterruptScope::enter(&[]);
assert!(!interrupt_requested());
assert!(charge(1));
}
{
let _scope = InterruptScope::enter(&[driver]);
assert!(!charge(1));
}
assert!(interrupt_requested());
assert_eq!(active_interrupts().len(), 1);
}
proptest::proptest! {
#[test]
fn interrupt_scope_observes_exactly_its_own_flags(
installed in proptest::collection::vec(proptest::bool::ANY, 0..4),
scoped in proptest::collection::vec(proptest::bool::ANY, 0..4),
nested in proptest::collection::vec(proptest::bool::ANY, 0..4),
) {
use proptest::prop_assert_eq;
let flags = |set: &[bool]| -> Vec<Arc<AtomicBool>> {
set.iter().map(|b| Arc::new(AtomicBool::new(*b))).collect()
};
let any = |set: &[bool]| set.iter().any(|b| *b);
let _guards: Vec<InterruptGuard> = flags(&installed)
.into_iter()
.map(InterruptGuard::install)
.collect();
prop_assert_eq!(interrupt_requested(), any(&installed));
{
let _scope = InterruptScope::enter(&flags(&scoped));
prop_assert_eq!(interrupt_requested(), any(&scoped));
prop_assert_eq!(charge(1), !any(&scoped));
{
let _nested = InterruptScope::enter(&flags(&nested));
prop_assert_eq!(interrupt_requested(), any(&nested));
prop_assert_eq!(active_interrupts().len(), nested.len());
}
prop_assert_eq!(interrupt_requested(), any(&scoped));
prop_assert_eq!(active_interrupts().len(), scoped.len());
}
prop_assert_eq!(interrupt_requested(), any(&installed));
prop_assert_eq!(active_interrupts().len(), installed.len());
}
}
}