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