ref_counts.rs

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