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 {
43 inner,
44 description: description.to_string(),
45 }
46 }
47
48 pub fn count(&self) -> usize {
49 self.inner.count.get()
50 }
51
52 pub fn ever_raised(&self) -> bool {
56 self.inner.raised_ever.get()
57 }
58
59 pub async fn wait_drained_event(&self) {
74 self.inner.drained.wait().await;
75 }
76
77 pub fn active(&self) -> Vec<String> {
79 self.inner.active.borrow().clone()
80 }
81
82 pub async fn wait_all_dropped(&self) {
83 if !self.inner.raised_ever.get() {
84 rustdv_sim::log::warning(
86 "all_objections_dropped awaited but no objection was ever raised",
87 );
88 return;
89 }
90 if self.inner.count.get() == 0 {
91 return;
92 }
93 self.inner.drained.wait().await;
94 }
95}
96
97pub struct ObjectionGuard {
99 inner: Rc<ObjInner>,
100 description: String,
101}
102
103impl Drop for ObjectionGuard {
104 fn drop(&mut self) {
105 let mut active = self.inner.active.borrow_mut();
106 if let Some(pos) = active.iter().position(|d| *d == self.description) {
107 active.remove(pos);
108 }
109 drop(active);
110 let n = self.inner.count.get().saturating_sub(1);
111 self.inner.count.set(n);
112 if n == 0 {
113 self.inner.drained.set();
114 }
115 }
116}
117
118#[cfg(test)]
123mod tests {
124 use super::*;
125 use rustdv_sim::testing::{assert_pending, block_on};
126
127 #[test]
128 fn a_guard_raises_and_dropping_it_drops() {
129 let reg = ObjectionRegistry::new();
130 assert_eq!(reg.count(), 0);
131 {
132 let _g = reg.raise("stimulus");
133 assert_eq!(reg.count(), 1);
134 }
135 assert_eq!(reg.count(), 0, "the guard dropped it");
136 }
137
138 #[test]
139 fn nested_objections_end_only_at_the_last_drop() {
140 let reg = ObjectionRegistry::new();
141 let a = reg.raise("a");
142 let b = reg.raise("b");
143 assert_eq!(reg.count(), 2);
144 drop(a);
145 assert_eq!(reg.count(), 1, "one left");
146 drop(b);
147 assert_eq!(reg.count(), 0);
148 }
149
150 #[test]
151 fn drained_fires_when_a_raised_count_returns_to_zero() {
152 block_on(async {
153 let reg = ObjectionRegistry::new();
154 let g = reg.raise("work");
155 let waiter = reg.clone();
156 rustdv_sim::executor::spawn(async move {
157 waiter.wait_drained_event().await;
158 });
159 drop(g);
160 reg.wait_drained_event().await;
162 });
163 }
164
165 #[test]
171 fn never_objecting_never_fires_the_drained_event() {
172 let reg = ObjectionRegistry::new();
173 assert_eq!(reg.count(), 0);
174 assert_pending(async move { reg.wait_drained_event().await });
175 }
176}