1use std::cell::RefCell;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
28
29#[derive(Debug)]
31pub struct GasMeter {
32 remaining: AtomicU64,
33}
34
35impl GasMeter {
36 pub fn new(credits: u64) -> Arc<Self> {
37 Arc::new(Self {
38 remaining: AtomicU64::new(credits),
39 })
40 }
41
42 pub fn remaining(&self) -> u64 {
43 self.remaining.load(Ordering::Relaxed)
44 }
45
46 pub fn charge(&self, cost: u64) -> bool {
49 self.remaining
50 .try_update(Ordering::Relaxed, Ordering::Relaxed, |remaining| {
51 remaining.checked_sub(cost)
52 })
53 .is_ok()
54 }
55}
56
57thread_local! {
58 static ACTIVE: RefCell<Vec<Arc<GasMeter>>> = const { RefCell::new(Vec::new()) };
59 static EXHAUSTED: RefCell<Vec<bool>> = const { RefCell::new(Vec::new()) };
60 static INTERRUPTS: RefCell<Vec<Arc<AtomicBool>>> = const { RefCell::new(Vec::new()) };
61}
62cljrs_gc::mutator_slot!("cljrs-runtime/GAS_ACTIVE", refcell ACTIVE: Vec<Arc<GasMeter>>);
63cljrs_gc::mutator_slot!("cljrs-runtime/GAS_EXHAUSTED", refcell EXHAUSTED: Vec<bool>);
64cljrs_gc::mutator_slot!("cljrs-runtime/GAS_INTERRUPTS", refcell INTERRUPTS: Vec<Arc<AtomicBool>>);
65
66#[must_use = "dropping InterruptGuard immediately uninstalls the interrupt flag"]
71pub struct InterruptGuard;
72
73impl InterruptGuard {
74 pub fn install(flag: Arc<AtomicBool>) -> Self {
75 INTERRUPTS.with(|flags| flags.borrow_mut().push(flag));
76 Self
77 }
78}
79
80impl Drop for InterruptGuard {
81 fn drop(&mut self) {
82 INTERRUPTS.with(|flags| {
83 flags.borrow_mut().pop();
84 });
85 }
86}
87
88pub fn interrupt_requested() -> bool {
90 INTERRUPTS.with(|flags| {
91 flags
92 .borrow()
93 .iter()
94 .any(|flag| flag.load(Ordering::Relaxed))
95 })
96}
97
98pub fn active_interrupts() -> Vec<Arc<AtomicBool>> {
101 INTERRUPTS.with(|flags| flags.borrow().clone())
102}
103
104#[must_use = "dropping InterruptScope immediately restores the previous interrupt flags"]
114pub struct InterruptScope {
115 outer: Vec<Arc<AtomicBool>>,
116}
117
118impl InterruptScope {
119 pub fn enter(flags: &[Arc<AtomicBool>]) -> Self {
120 let outer = INTERRUPTS.with(|installed| installed.replace(flags.to_vec()));
121 Self { outer }
122 }
123}
124
125impl Drop for InterruptScope {
126 fn drop(&mut self) {
127 INTERRUPTS.with(|installed| {
128 installed.replace(std::mem::take(&mut self.outer));
129 });
130 }
131}
132
133#[must_use = "dropping GasGuard immediately uninstalls the active gas meter"]
135pub struct GasGuard;
136
137impl GasGuard {
138 pub fn install(meter: Arc<GasMeter>) -> Self {
139 ACTIVE.with(|active| active.borrow_mut().push(meter));
140 EXHAUSTED.with(|exhausted| exhausted.borrow_mut().push(false));
141 Self
142 }
143}
144
145impl Drop for GasGuard {
146 fn drop(&mut self) {
147 ACTIVE.with(|active| {
148 let mut active = active.borrow_mut();
149 active.pop();
150 });
151 EXHAUSTED.with(|exhausted| {
152 exhausted.borrow_mut().pop();
153 });
154 }
155}
156
157pub fn charge(cost: u64) -> bool {
159 if interrupt_requested() || is_exhausted() {
160 return false;
161 }
162 let charged = ACTIVE.with(|active| {
163 let active = active.borrow();
164 if active.is_empty() {
165 return true;
166 }
167 if active.iter().any(|meter| meter.remaining() < cost) {
168 return false;
169 }
170 active.iter().all(|meter| meter.charge(cost))
171 });
172 if !charged {
173 ACTIVE.with(|active| {
174 let active = active.borrow();
175 EXHAUSTED.with(|exhausted| {
176 for (index, meter) in active.iter().enumerate() {
177 if meter.remaining() < cost {
178 exhausted.borrow_mut()[index] = true;
179 }
180 }
181 });
182 });
183 }
184 charged
185}
186
187pub fn is_exhausted() -> bool {
189 interrupt_requested()
190 || EXHAUSTED.with(|exhausted| exhausted.borrow().iter().any(|value| *value))
191}
192
193pub fn active_meters() -> Vec<Arc<GasMeter>> {
195 ACTIVE.with(|active| active.borrow().clone())
196}
197
198pub fn install_meters(meters: &[Arc<GasMeter>]) -> Vec<GasGuard> {
200 meters.iter().cloned().map(GasGuard::install).collect()
201}
202
203pub fn take_exhausted() -> bool {
208 EXHAUSTED.with(|exhausted| {
209 let mut exhausted = exhausted.borrow_mut();
210 let was_exhausted = exhausted.iter().any(|value| *value);
211 exhausted.fill(false);
212 was_exhausted
213 })
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn scoped_meter_charges_without_partial_consumption() {
222 let meter = GasMeter::new(3);
223 let _guard = GasGuard::install(meter.clone());
224 assert!(charge(2));
225 assert!(!charge(2));
226 assert_eq!(meter.remaining(), 1);
227 }
228
229 #[test]
230 fn nested_meters_charge_outer_budget() {
231 let outer = GasMeter::new(3);
232 let _outer_guard = GasGuard::install(outer.clone());
233 let inner = GasMeter::new(2);
234 let _inner_guard = GasGuard::install(inner.clone());
235 assert!(charge(2));
236 assert_eq!(outer.remaining(), 1);
237 assert_eq!(inner.remaining(), 0);
238 assert!(!charge(1));
239 }
240
241 #[test]
242 fn inner_exhaustion_does_not_poison_healthy_outer_scope() {
243 let outer = GasMeter::new(10);
244 let _outer_guard = GasGuard::install(outer.clone());
245 {
246 let inner = GasMeter::new(0);
247 let _inner_guard = GasGuard::install(inner);
248 assert!(!charge(1));
249 assert!(is_exhausted());
250 }
251 assert!(!is_exhausted());
252 assert!(charge(1));
253 assert_eq!(outer.remaining(), 9);
254 }
255
256 #[test]
257 fn interrupt_flag_fails_charges_until_uninstalled() {
258 let flag = Arc::new(AtomicBool::new(false));
259 {
260 let _guard = InterruptGuard::install(flag.clone());
261 assert!(charge(1));
262 flag.store(true, Ordering::Relaxed);
263 assert!(!charge(1));
264 assert!(is_exhausted());
265 assert!(interrupt_requested());
266 }
267 assert!(!interrupt_requested());
268 assert!(!is_exhausted());
269 assert!(charge(1));
270 }
271
272 #[test]
273 fn interrupt_scope_replaces_the_installed_flags_and_restores_them() {
274 let driver = Arc::new(AtomicBool::new(true));
275 let _guard = InterruptGuard::install(driver.clone());
276 assert!(!charge(1));
277 {
278 let _scope = InterruptScope::enter(&[]);
280 assert!(!interrupt_requested());
281 assert!(charge(1));
282 }
283 {
284 let _scope = InterruptScope::enter(&[driver]);
286 assert!(!charge(1));
287 }
288 assert!(interrupt_requested());
289 assert_eq!(active_interrupts().len(), 1);
290 }
291
292 proptest::proptest! {
293 #[test]
297 fn interrupt_scope_observes_exactly_its_own_flags(
298 installed in proptest::collection::vec(proptest::bool::ANY, 0..4),
299 scoped in proptest::collection::vec(proptest::bool::ANY, 0..4),
300 nested in proptest::collection::vec(proptest::bool::ANY, 0..4),
301 ) {
302 use proptest::prop_assert_eq;
303 let flags = |set: &[bool]| -> Vec<Arc<AtomicBool>> {
304 set.iter().map(|b| Arc::new(AtomicBool::new(*b))).collect()
305 };
306 let any = |set: &[bool]| set.iter().any(|b| *b);
307
308 let _guards: Vec<InterruptGuard> = flags(&installed)
309 .into_iter()
310 .map(InterruptGuard::install)
311 .collect();
312 prop_assert_eq!(interrupt_requested(), any(&installed));
313 {
314 let _scope = InterruptScope::enter(&flags(&scoped));
315 prop_assert_eq!(interrupt_requested(), any(&scoped));
316 prop_assert_eq!(charge(1), !any(&scoped));
317 {
318 let _nested = InterruptScope::enter(&flags(&nested));
319 prop_assert_eq!(interrupt_requested(), any(&nested));
320 prop_assert_eq!(active_interrupts().len(), nested.len());
321 }
322 prop_assert_eq!(interrupt_requested(), any(&scoped));
323 prop_assert_eq!(active_interrupts().len(), scoped.len());
324 }
325 prop_assert_eq!(interrupt_requested(), any(&installed));
326 prop_assert_eq!(active_interrupts().len(), installed.len());
327 }
328 }
329}