rustdv_methodology/
objection.rs1use std::cell::{Cell, RefCell};
7use std::rc::Rc;
8
9use rustdv_sim::sync::Event;
10
11struct ObjInner {
12 count: Cell<usize>,
13 drained: Event,
14 raised_ever: Cell<bool>,
15 active: RefCell<Vec<String>>,
16}
17
18#[derive(Clone)]
19pub struct ObjectionRegistry {
20 inner: Rc<ObjInner>,
21}
22
23impl ObjectionRegistry {
24 #[allow(clippy::new_without_default)]
25 pub fn new() -> ObjectionRegistry {
26 ObjectionRegistry {
27 inner: Rc::new(ObjInner {
28 count: Cell::new(0),
29 drained: Event::new(),
30 raised_ever: Cell::new(false),
31 active: RefCell::new(Vec::new()),
32 }),
33 }
34 }
35
36 pub fn raise(&self, description: &str) -> ObjectionGuard {
37 let inner = self.inner.clone();
38 inner.count.set(inner.count.get() + 1);
39 inner.raised_ever.set(true);
40 inner.drained.clear();
41 inner.active.borrow_mut().push(description.to_string());
42 ObjectionGuard { inner, description: description.to_string() }
43 }
44
45 pub fn count(&self) -> usize {
46 self.inner.count.get()
47 }
48
49 pub fn ever_raised(&self) -> bool {
53 self.inner.raised_ever.get()
54 }
55
56 pub async fn wait_drained_event(&self) {
71 self.inner.drained.wait().await;
72 }
73
74 pub fn active(&self) -> Vec<String> {
76 self.inner.active.borrow().clone()
77 }
78
79 pub async fn wait_all_dropped(&self) {
80 if !self.inner.raised_ever.get() {
81 rustdv_sim::log::warning(
83 "all_objections_dropped awaited but no objection was ever raised",
84 );
85 return;
86 }
87 if self.inner.count.get() == 0 {
88 return;
89 }
90 self.inner.drained.wait().await;
91 }
92}
93
94pub struct ObjectionGuard {
96 inner: Rc<ObjInner>,
97 description: String,
98}
99
100impl Drop for ObjectionGuard {
101 fn drop(&mut self) {
102 let mut active = self.inner.active.borrow_mut();
103 if let Some(pos) = active.iter().position(|d| *d == self.description) {
104 active.remove(pos);
105 }
106 drop(active);
107 let n = self.inner.count.get().saturating_sub(1);
108 self.inner.count.set(n);
109 if n == 0 {
110 self.inner.drained.set();
111 }
112 }
113}
114
115#[cfg(test)]
120mod tests {
121 use super::*;
122 use rustdv_sim::testing::{assert_pending, block_on};
123
124 #[test]
125 fn a_guard_raises_and_dropping_it_drops() {
126 let reg = ObjectionRegistry::new();
127 assert_eq!(reg.count(), 0);
128 {
129 let _g = reg.raise("stimulus");
130 assert_eq!(reg.count(), 1);
131 }
132 assert_eq!(reg.count(), 0, "the guard dropped it");
133 }
134
135 #[test]
136 fn nested_objections_end_only_at_the_last_drop() {
137 let reg = ObjectionRegistry::new();
138 let a = reg.raise("a");
139 let b = reg.raise("b");
140 assert_eq!(reg.count(), 2);
141 drop(a);
142 assert_eq!(reg.count(), 1, "one left");
143 drop(b);
144 assert_eq!(reg.count(), 0);
145 }
146
147 #[test]
148 fn drained_fires_when_a_raised_count_returns_to_zero() {
149 block_on(async {
150 let reg = ObjectionRegistry::new();
151 let g = reg.raise("work");
152 let waiter = reg.clone();
153 rustdv_sim::executor::spawn(async move {
154 waiter.wait_drained_event().await;
155 });
156 drop(g);
157 reg.wait_drained_event().await;
159 });
160 }
161
162 #[test]
168 fn never_objecting_never_fires_the_drained_event() {
169 let reg = ObjectionRegistry::new();
170 assert_eq!(reg.count(), 0);
171 assert_pending(async move { reg.wait_drained_event().await });
172 }
173}