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}