use std::cell::RefCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use cljrs_value::parking::Event;
#[derive(Debug, Default)]
pub struct InterruptFlag {
set: AtomicBool,
raised: Event,
}
impl InterruptFlag {
pub fn new(set: bool) -> Self {
Self {
set: AtomicBool::new(set),
raised: Event::default(),
}
}
pub fn is_set(&self) -> bool {
self.set.load(Ordering::SeqCst)
}
pub fn request(&self) {
self.set.store(true, Ordering::SeqCst);
self.raised.notify_all();
}
}
pub type InterruptFlags = Arc<[Arc<InterruptFlag>]>;
#[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<InterruptFlags> = RefCell::new(InterruptFlags::default());
}
cljrs_gc::mutator_slot!("cljrs-runtime/GAS_ACTIVE", refcell ACTIVE: Vec<Arc<GasMeter>>);
cljrs_gc::mutator_slot!("cljrs-runtime/GAS_EXHAUSTED", refcell EXHAUSTED: Vec<bool>);
cljrs_gc::mutator_slot!("cljrs-runtime/GAS_INTERRUPTS", refcell INTERRUPTS: InterruptFlags);
#[must_use = "dropping InterruptGuard immediately uninstalls the interrupt flag"]
pub struct InterruptGuard;
impl InterruptGuard {
pub fn install(flag: Arc<InterruptFlag>) -> Self {
INTERRUPTS.with(|flags| {
let mut flags = flags.borrow_mut();
*flags = flags.iter().cloned().chain([flag]).collect();
});
Self
}
}
impl Drop for InterruptGuard {
fn drop(&mut self) {
INTERRUPTS.with(|flags| {
let mut flags = flags.borrow_mut();
let len = flags.len().saturating_sub(1);
*flags = flags[..len].iter().cloned().collect();
});
}
}
pub fn request_interrupt(flag: &InterruptFlag) {
flag.request();
}
pub async fn interrupted(flags: &[Arc<InterruptFlag>]) {
match flags {
[] => std::future::pending().await,
[flag] => loop {
let listener = flag.raised.listen();
if flag.is_set() {
return;
}
listener.await;
},
_ => loop {
let mut listeners: Vec<_> = flags.iter().map(|flag| flag.raised.listen()).collect();
if flags.iter().any(|flag| flag.is_set()) {
return;
}
std::future::poll_fn(|cx| {
let mut ready = false;
for listener in &mut listeners {
ready |= std::future::Future::poll(std::pin::Pin::new(listener), cx).is_ready();
}
if ready {
std::task::Poll::Ready(())
} else {
std::task::Poll::Pending
}
})
.await;
},
}
}
pub fn interrupt_requested() -> bool {
INTERRUPTS.with(|flags| {
flags
.borrow()
.iter()
.any(|flag| flag.set.load(Ordering::Relaxed))
})
}
pub fn active_interrupts() -> InterruptFlags {
INTERRUPTS.with(|flags| flags.borrow().clone())
}
#[must_use = "dropping InterruptScope immediately restores the previous interrupt flags"]
pub struct InterruptScope {
outer: InterruptFlags,
}
impl InterruptScope {
pub fn enter(flags: &InterruptFlags) -> Self {
let outer = INTERRUPTS.with(|installed| installed.replace(flags.clone()));
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(InterruptFlag::new(false));
{
let _guard = InterruptGuard::install(flag.clone());
assert!(charge(1));
flag.request();
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(InterruptFlag::new(true));
let _guard = InterruptGuard::install(driver.clone());
assert!(!charge(1));
{
let _scope = InterruptScope::enter(&InterruptFlags::default());
assert!(!interrupt_requested());
assert!(charge(1));
}
{
let _scope = InterruptScope::enter(&InterruptFlags::from([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]| -> InterruptFlags {
set.iter().map(|b| Arc::new(InterruptFlag::new(*b))).collect()
};
let any = |set: &[bool]| set.iter().any(|b| *b);
let _guards: Vec<InterruptGuard> = flags(&installed)
.iter()
.cloned()
.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());
}
}
}