1use std::sync::atomic::{AtomicUsize, Ordering::Acquire, Ordering::Relaxed, Ordering::Release};
3use std::{fmt, ptr::NonNull, sync::atomic::fence};
4
5use crate::stvec::abort;
6
7const MAX_REFS: usize = isize::MAX as usize;
8
9#[cfg_attr(docsrs, doc(cfg(feature = "tracker")))]
18#[repr(transparent)]
19pub struct BytesTracker(NonNull<TrackerInner>);
20
21const _: () = assert!(size_of::<BytesTracker>() == size_of::<*const TrackerInner>());
22
23#[cfg_attr(docsrs, doc(cfg(feature = "tracker")))]
28#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
29#[non_exhaustive]
30pub struct BytesTrackerSnapshot {
31 pub live_bytes: usize,
33 pub live_allocations: usize,
35}
36
37pub(crate) struct TrackerInner {
38 refs: AtomicUsize,
40 handles: AtomicUsize,
42 live_bytes: AtomicUsize,
43}
44
45unsafe impl Send for BytesTracker {}
47unsafe impl Sync for BytesTracker {}
49
50impl BytesTracker {
51 pub fn new() -> Self {
53 let inner = Box::new(TrackerInner {
54 refs: AtomicUsize::new(1),
55 handles: AtomicUsize::new(1),
56 live_bytes: AtomicUsize::new(0),
57 });
58 BytesTracker(NonNull::from(Box::leak(inner)))
59 }
60
61 pub fn snapshot(&self) -> BytesTrackerSnapshot {
63 let inner = self.inner();
64 let handles = inner.handles.load(Relaxed);
65 let refs = inner.refs.load(Relaxed);
66 BytesTrackerSnapshot {
67 live_bytes: inner.live_bytes.load(Relaxed),
68 live_allocations: refs.saturating_sub(handles),
70 }
71 }
72
73 pub fn ptr_eq(&self, other: &BytesTracker) -> bool {
75 self.0 == other.0
76 }
77
78 pub(crate) fn as_inner(&self) -> *const TrackerInner {
79 self.0.as_ptr()
80 }
81
82 fn inner(&self) -> &TrackerInner {
83 unsafe { self.0.as_ref() }
85 }
86}
87
88impl Default for BytesTracker {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl Clone for BytesTracker {
95 fn clone(&self) -> Self {
96 let inner = self.inner();
97 if inner.refs.fetch_add(1, Relaxed) > MAX_REFS {
98 abort();
99 }
100 inner.handles.fetch_add(1, Relaxed);
101 BytesTracker(self.0)
102 }
103}
104
105impl Drop for BytesTracker {
106 fn drop(&mut self) {
107 self.inner().handles.fetch_sub(1, Relaxed);
108 unsafe { TrackerInner::release(self.0.as_ptr()) }
110 }
111}
112
113impl fmt::Debug for BytesTracker {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 let snapshot = self.snapshot();
116 f.debug_struct("BytesTracker")
117 .field("live_bytes", &snapshot.live_bytes)
118 .field("live_allocations", &snapshot.live_allocations)
119 .finish()
120 }
121}
122
123impl TrackerInner {
124 pub(crate) fn charge(&self, size: usize) {
127 if self.refs.fetch_add(1, Relaxed) > MAX_REFS {
128 abort();
129 }
130 self.live_bytes.fetch_add(size, Relaxed);
131 }
132
133 pub(crate) fn resize(&self, old: usize, new: usize) {
135 if new > old {
136 self.live_bytes.fetch_add(new - old, Relaxed);
137 } else {
138 self.live_bytes.fetch_sub(old - new, Relaxed);
139 }
140 }
141
142 pub(crate) unsafe fn uncharge(this: *const TrackerInner, size: usize) {
149 unsafe { (*this).live_bytes.fetch_sub(size, Relaxed) };
151 unsafe { TrackerInner::release(this) }
153 }
154
155 unsafe fn release(this: *const TrackerInner) {
159 if unsafe { (*this).refs.fetch_sub(1, Release) } != 1 {
161 return;
162 }
163 fence(Acquire);
164 drop(unsafe { Box::from_raw(this.cast_mut()) });
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 fn snap(bytes: usize, allocs: usize) -> BytesTrackerSnapshot {
174 BytesTrackerSnapshot {
175 live_bytes: bytes,
176 live_allocations: allocs,
177 }
178 }
179
180 #[test]
181 fn charge_resize_uncharge() {
182 let tracker = BytesTracker::new();
183 assert_eq!(tracker.snapshot(), snap(0, 0));
184
185 let inner = tracker.as_inner();
186 unsafe { (*inner).charge(100) };
188 unsafe { (*inner).charge(50) };
189 assert_eq!(tracker.snapshot(), snap(150, 2));
190
191 unsafe { (*inner).resize(100, 300) };
192 assert_eq!(tracker.snapshot(), snap(350, 2));
193 unsafe { (*inner).resize(300, 200) };
194 unsafe { (*inner).resize(200, 200) };
195 assert_eq!(tracker.snapshot(), snap(250, 2));
196
197 unsafe { TrackerInner::uncharge(inner, 200) };
198 assert_eq!(tracker.snapshot(), snap(50, 1));
199 unsafe { TrackerInner::uncharge(inner, 50) };
200 assert_eq!(tracker.snapshot(), snap(0, 0));
201 assert_eq!(
202 format!("{tracker:?}"),
203 "BytesTracker { live_bytes: 0, live_allocations: 0 }"
204 );
205 }
206
207 #[test]
208 fn handles() {
209 let tracker = BytesTracker::default();
210 let tracker2 = tracker.clone();
211 assert!(tracker.ptr_eq(&tracker2));
212 assert!(!tracker.ptr_eq(&BytesTracker::new()));
213
214 assert_eq!(tracker.snapshot(), snap(0, 0));
216 unsafe { (*tracker.as_inner()).charge(10) };
217 assert_eq!(tracker2.snapshot(), snap(10, 1));
218 drop(tracker2);
219 assert_eq!(tracker.snapshot(), snap(10, 1));
220 unsafe { TrackerInner::uncharge(tracker.as_inner(), 10) };
221 assert_eq!(tracker.snapshot(), snap(0, 0));
222 }
223
224 #[test]
225 fn allocation_outlives_handles() {
226 let tracker = BytesTracker::new();
227 let inner = tracker.as_inner();
228 unsafe { (*inner).charge(10) };
229 unsafe { (*inner).charge(20) };
230 drop(tracker);
231 unsafe { TrackerInner::uncharge(inner, 10) };
233 unsafe { TrackerInner::uncharge(inner, 20) };
234 }
235
236 #[test]
237 fn concurrent() {
238 const THREADS: usize = 4;
239 const ITERS: usize = if cfg!(miri) { 20 } else { 10_000 };
240
241 let tracker = BytesTracker::new();
242 let threads: Vec<_> = (0..THREADS)
243 .map(|_| {
244 let tracker = tracker.clone();
245 std::thread::spawn(move || {
246 let inner = tracker.as_inner();
247 for i in 0..ITERS {
248 let handle = tracker.clone();
249 unsafe { (*inner).charge(i) };
250 unsafe { (*inner).resize(i, i + 1) };
251 drop(handle);
252 unsafe { TrackerInner::uncharge(inner, i + 1) };
253 }
254 unsafe { (*inner).charge(1) };
256 })
257 })
258 .collect();
259 for t in threads {
260 t.join().unwrap();
261 }
262 assert_eq!(tracker.snapshot(), snap(THREADS, THREADS));
263
264 let inner = tracker.as_inner();
265 drop(tracker);
266 for _ in 0..THREADS {
267 unsafe { TrackerInner::uncharge(inner, 1) };
268 }
269 }
270}