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}
62
63#[must_use = "dropping InterruptGuard immediately uninstalls the interrupt flag"]
68pub struct InterruptGuard;
69
70impl InterruptGuard {
71 pub fn install(flag: Arc<AtomicBool>) -> Self {
72 INTERRUPTS.with(|flags| flags.borrow_mut().push(flag));
73 Self
74 }
75}
76
77impl Drop for InterruptGuard {
78 fn drop(&mut self) {
79 INTERRUPTS.with(|flags| {
80 flags.borrow_mut().pop();
81 });
82 }
83}
84
85pub fn interrupt_requested() -> bool {
87 INTERRUPTS.with(|flags| {
88 flags
89 .borrow()
90 .iter()
91 .any(|flag| flag.load(Ordering::Relaxed))
92 })
93}
94
95pub fn active_interrupts() -> Vec<Arc<AtomicBool>> {
98 INTERRUPTS.with(|flags| flags.borrow().clone())
99}
100
101#[must_use = "dropping InterruptScope immediately restores the previous interrupt flags"]
111pub struct InterruptScope {
112 outer: Vec<Arc<AtomicBool>>,
113}
114
115impl InterruptScope {
116 pub fn enter(flags: &[Arc<AtomicBool>]) -> Self {
117 let outer = INTERRUPTS.with(|installed| installed.replace(flags.to_vec()));
118 Self { outer }
119 }
120}
121
122impl Drop for InterruptScope {
123 fn drop(&mut self) {
124 INTERRUPTS.with(|installed| {
125 installed.replace(std::mem::take(&mut self.outer));
126 });
127 }
128}
129
130#[must_use = "dropping GasGuard immediately uninstalls the active gas meter"]
132pub struct GasGuard;
133
134impl GasGuard {
135 pub fn install(meter: Arc<GasMeter>) -> Self {
136 ACTIVE.with(|active| active.borrow_mut().push(meter));
137 EXHAUSTED.with(|exhausted| exhausted.borrow_mut().push(false));
138 Self
139 }
140}
141
142impl Drop for GasGuard {
143 fn drop(&mut self) {
144 ACTIVE.with(|active| {
145 let mut active = active.borrow_mut();
146 active.pop();
147 });
148 EXHAUSTED.with(|exhausted| {
149 exhausted.borrow_mut().pop();
150 });
151 }
152}
153
154pub fn charge(cost: u64) -> bool {
156 if interrupt_requested() || is_exhausted() {
157 return false;
158 }
159 let charged = ACTIVE.with(|active| {
160 let active = active.borrow();
161 if active.is_empty() {
162 return true;
163 }
164 if active.iter().any(|meter| meter.remaining() < cost) {
165 return false;
166 }
167 active.iter().all(|meter| meter.charge(cost))
168 });
169 if !charged {
170 ACTIVE.with(|active| {
171 let active = active.borrow();
172 EXHAUSTED.with(|exhausted| {
173 for (index, meter) in active.iter().enumerate() {
174 if meter.remaining() < cost {
175 exhausted.borrow_mut()[index] = true;
176 }
177 }
178 });
179 });
180 }
181 charged
182}
183
184pub fn is_exhausted() -> bool {
186 interrupt_requested()
187 || EXHAUSTED.with(|exhausted| exhausted.borrow().iter().any(|value| *value))
188}
189
190pub fn active_meters() -> Vec<Arc<GasMeter>> {
192 ACTIVE.with(|active| active.borrow().clone())
193}
194
195pub fn install_meters(meters: &[Arc<GasMeter>]) -> Vec<GasGuard> {
197 meters.iter().cloned().map(GasGuard::install).collect()
198}
199
200pub fn take_exhausted() -> bool {
205 EXHAUSTED.with(|exhausted| {
206 let mut exhausted = exhausted.borrow_mut();
207 let was_exhausted = exhausted.iter().any(|value| *value);
208 exhausted.fill(false);
209 was_exhausted
210 })
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn scoped_meter_charges_without_partial_consumption() {
219 let meter = GasMeter::new(3);
220 let _guard = GasGuard::install(meter.clone());
221 assert!(charge(2));
222 assert!(!charge(2));
223 assert_eq!(meter.remaining(), 1);
224 }
225
226 #[test]
227 fn nested_meters_charge_outer_budget() {
228 let outer = GasMeter::new(3);
229 let _outer_guard = GasGuard::install(outer.clone());
230 let inner = GasMeter::new(2);
231 let _inner_guard = GasGuard::install(inner.clone());
232 assert!(charge(2));
233 assert_eq!(outer.remaining(), 1);
234 assert_eq!(inner.remaining(), 0);
235 assert!(!charge(1));
236 }
237
238 #[test]
239 fn inner_exhaustion_does_not_poison_healthy_outer_scope() {
240 let outer = GasMeter::new(10);
241 let _outer_guard = GasGuard::install(outer.clone());
242 {
243 let inner = GasMeter::new(0);
244 let _inner_guard = GasGuard::install(inner);
245 assert!(!charge(1));
246 assert!(is_exhausted());
247 }
248 assert!(!is_exhausted());
249 assert!(charge(1));
250 assert_eq!(outer.remaining(), 9);
251 }
252
253 #[test]
254 fn interrupt_flag_fails_charges_until_uninstalled() {
255 let flag = Arc::new(AtomicBool::new(false));
256 {
257 let _guard = InterruptGuard::install(flag.clone());
258 assert!(charge(1));
259 flag.store(true, Ordering::Relaxed);
260 assert!(!charge(1));
261 assert!(is_exhausted());
262 assert!(interrupt_requested());
263 }
264 assert!(!interrupt_requested());
265 assert!(!is_exhausted());
266 assert!(charge(1));
267 }
268
269 #[test]
270 fn interrupt_scope_replaces_the_installed_flags_and_restores_them() {
271 let driver = Arc::new(AtomicBool::new(true));
272 let _guard = InterruptGuard::install(driver.clone());
273 assert!(!charge(1));
274 {
275 let _scope = InterruptScope::enter(&[]);
277 assert!(!interrupt_requested());
278 assert!(charge(1));
279 }
280 {
281 let _scope = InterruptScope::enter(&[driver]);
283 assert!(!charge(1));
284 }
285 assert!(interrupt_requested());
286 assert_eq!(active_interrupts().len(), 1);
287 }
288
289 proptest::proptest! {
290 #[test]
294 fn interrupt_scope_observes_exactly_its_own_flags(
295 installed in proptest::collection::vec(proptest::bool::ANY, 0..4),
296 scoped in proptest::collection::vec(proptest::bool::ANY, 0..4),
297 nested in proptest::collection::vec(proptest::bool::ANY, 0..4),
298 ) {
299 use proptest::prop_assert_eq;
300 let flags = |set: &[bool]| -> Vec<Arc<AtomicBool>> {
301 set.iter().map(|b| Arc::new(AtomicBool::new(*b))).collect()
302 };
303 let any = |set: &[bool]| set.iter().any(|b| *b);
304
305 let _guards: Vec<InterruptGuard> = flags(&installed)
306 .into_iter()
307 .map(InterruptGuard::install)
308 .collect();
309 prop_assert_eq!(interrupt_requested(), any(&installed));
310 {
311 let _scope = InterruptScope::enter(&flags(&scoped));
312 prop_assert_eq!(interrupt_requested(), any(&scoped));
313 prop_assert_eq!(charge(1), !any(&scoped));
314 {
315 let _nested = InterruptScope::enter(&flags(&nested));
316 prop_assert_eq!(interrupt_requested(), any(&nested));
317 prop_assert_eq!(active_interrupts().len(), nested.len());
318 }
319 prop_assert_eq!(interrupt_requested(), any(&scoped));
320 prop_assert_eq!(active_interrupts().len(), scoped.len());
321 }
322 prop_assert_eq!(interrupt_requested(), any(&installed));
323 prop_assert_eq!(active_interrupts().len(), installed.len());
324 }
325 }
326}