ref_counts.rs

  1#[cfg(any(test, feature = "test-support"))]
  2use std::sync::Arc;
  3
  4use lazy_static::lazy_static;
  5#[cfg(any(test, feature = "test-support"))]
  6use parking_lot::Mutex;
  7
  8use collections::{hash_map::Entry, HashMap, HashSet};
  9
 10#[cfg(any(test, feature = "test-support"))]
 11use crate::util::post_inc;
 12use crate::ElementStateId;
 13
 14lazy_static! {
 15    static ref LEAK_BACKTRACE: bool =
 16        std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
 17}
 18
 19struct ElementStateRefCount {
 20    ref_count: usize,
 21    frame_id: usize,
 22}
 23
 24#[derive(Default)]
 25pub struct RefCounts {
 26    entity_counts: HashMap<usize, usize>,
 27    element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
 28    dropped_windows: HashSet<usize>,
 29    dropped_models: HashSet<usize>,
 30    dropped_views: HashSet<(usize, usize)>,
 31    dropped_element_states: HashSet<ElementStateId>,
 32
 33    #[cfg(any(test, feature = "test-support"))]
 34    pub leak_detector: Arc<Mutex<LeakDetector>>,
 35}
 36
 37impl RefCounts {
 38    #[cfg(any(test, feature = "test-support"))]
 39    pub fn new(leak_detector: Arc<Mutex<LeakDetector>>) -> Self {
 40        Self {
 41            #[cfg(any(test, feature = "test-support"))]
 42            leak_detector,
 43            ..Default::default()
 44        }
 45    }
 46
 47    pub fn inc_window(&mut self, window_id: usize) {
 48        match self.entity_counts.entry(window_id) {
 49            Entry::Occupied(mut entry) => {
 50                *entry.get_mut() += 1;
 51            }
 52            Entry::Vacant(entry) => {
 53                entry.insert(1);
 54                self.dropped_windows.remove(&window_id);
 55            }
 56        }
 57    }
 58
 59    pub fn inc_model(&mut self, model_id: usize) {
 60        match self.entity_counts.entry(model_id) {
 61            Entry::Occupied(mut entry) => {
 62                *entry.get_mut() += 1;
 63            }
 64            Entry::Vacant(entry) => {
 65                entry.insert(1);
 66                self.dropped_models.remove(&model_id);
 67            }
 68        }
 69    }
 70
 71    pub fn inc_view(&mut self, window_id: usize, view_id: usize) {
 72        match self.entity_counts.entry(view_id) {
 73            Entry::Occupied(mut entry) => *entry.get_mut() += 1,
 74            Entry::Vacant(entry) => {
 75                entry.insert(1);
 76                self.dropped_views.remove(&(window_id, view_id));
 77            }
 78        }
 79    }
 80
 81    pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
 82        match self.element_state_counts.entry(id) {
 83            Entry::Occupied(mut entry) => {
 84                let entry = entry.get_mut();
 85                if entry.frame_id == frame_id || entry.ref_count >= 2 {
 86                    panic!("used the same element state more than once in the same frame");
 87                }
 88                entry.ref_count += 1;
 89                entry.frame_id = frame_id;
 90            }
 91            Entry::Vacant(entry) => {
 92                entry.insert(ElementStateRefCount {
 93                    ref_count: 1,
 94                    frame_id,
 95                });
 96                self.dropped_element_states.remove(&id);
 97            }
 98        }
 99    }
100
101    pub fn dec_window(&mut self, window_id: usize) {
102        let count = self.entity_counts.get_mut(&window_id).unwrap();
103        *count -= 1;
104        if *count == 0 {
105            self.entity_counts.remove(&window_id);
106            self.dropped_windows.insert(window_id);
107        }
108    }
109
110    pub fn dec_model(&mut self, model_id: usize) {
111        let count = self.entity_counts.get_mut(&model_id).unwrap();
112        *count -= 1;
113        if *count == 0 {
114            self.entity_counts.remove(&model_id);
115            self.dropped_models.insert(model_id);
116        }
117    }
118
119    pub fn dec_view(&mut self, window_id: usize, view_id: usize) {
120        let count = self.entity_counts.get_mut(&view_id).unwrap();
121        *count -= 1;
122        if *count == 0 {
123            self.entity_counts.remove(&view_id);
124            self.dropped_views.insert((window_id, view_id));
125        }
126    }
127
128    pub fn dec_element_state(&mut self, id: ElementStateId) {
129        let entry = self.element_state_counts.get_mut(&id).unwrap();
130        entry.ref_count -= 1;
131        if entry.ref_count == 0 {
132            self.element_state_counts.remove(&id);
133            self.dropped_element_states.insert(id);
134        }
135    }
136
137    pub fn is_entity_alive(&self, entity_id: usize) -> bool {
138        self.entity_counts.contains_key(&entity_id)
139    }
140
141    pub fn take_dropped(
142        &mut self,
143    ) -> (
144        HashSet<usize>,
145        HashSet<(usize, usize)>,
146        HashSet<ElementStateId>,
147    ) {
148        (
149            std::mem::take(&mut self.dropped_models),
150            std::mem::take(&mut self.dropped_views),
151            std::mem::take(&mut self.dropped_element_states),
152        )
153    }
154}
155
156#[cfg(any(test, feature = "test-support"))]
157#[derive(Default)]
158pub struct LeakDetector {
159    next_handle_id: usize,
160    #[allow(clippy::type_complexity)]
161    handle_backtraces: HashMap<
162        usize,
163        (
164            Option<&'static str>,
165            HashMap<usize, Option<backtrace::Backtrace>>,
166        ),
167    >,
168}
169
170#[cfg(any(test, feature = "test-support"))]
171impl LeakDetector {
172    pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
173        let handle_id = post_inc(&mut self.next_handle_id);
174        let entry = self.handle_backtraces.entry(entity_id).or_default();
175        let backtrace = if *LEAK_BACKTRACE {
176            Some(backtrace::Backtrace::new_unresolved())
177        } else {
178            None
179        };
180        if let Some(type_name) = type_name {
181            entry.0.get_or_insert(type_name);
182        }
183        entry.1.insert(handle_id, backtrace);
184        handle_id
185    }
186
187    pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
188        if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
189            assert!(backtraces.remove(&handle_id).is_some());
190            if backtraces.is_empty() {
191                self.handle_backtraces.remove(&entity_id);
192            }
193        }
194    }
195
196    pub fn assert_dropped(&mut self, entity_id: usize) {
197        if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
198            for trace in backtraces.values_mut().flatten() {
199                trace.resolve();
200                eprintln!("{:?}", crate::util::CwdBacktrace(trace));
201            }
202
203            let hint = if *LEAK_BACKTRACE {
204                ""
205            } else {
206                " – set LEAK_BACKTRACE=1 for more information"
207            };
208
209            panic!(
210                "{} handles to {} {} still exist{}",
211                backtraces.len(),
212                type_name.unwrap_or("entity"),
213                entity_id,
214                hint
215            );
216        }
217    }
218
219    pub fn detect(&mut self) {
220        let mut found_leaks = false;
221        for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
222            eprintln!(
223                "leaked {} handles to {} {}",
224                backtraces.len(),
225                type_name.unwrap_or("entity"),
226                id
227            );
228            for trace in backtraces.values_mut().flatten() {
229                trace.resolve();
230                eprintln!("{:?}", crate::util::CwdBacktrace(trace));
231            }
232            found_leaks = true;
233        }
234
235        let hint = if *LEAK_BACKTRACE {
236            ""
237        } else {
238            " – set LEAK_BACKTRACE=1 for more information"
239        };
240        assert!(!found_leaks, "detected leaked handles{}", hint);
241    }
242}