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}