tracing_rc/sync/
mod.rs

1use std::{
2    mem::ManuallyDrop,
3    ops::Deref,
4    sync::{
5        Arc,
6        Weak,
7    },
8};
9
10use atomic::Atomic;
11use bytemuck::NoUninit;
12use parking_lot::{
13    RwLock,
14    RwLockReadGuard,
15};
16
17/// Notes:
18/// There are several moving parts the collector needs to care about in order to prevent leaks or
19/// early drop.
20///
21/// 1. The reference count of traced objects. This may change at any time.
22/// 2. The graph of child components. This may be altered any time a reference is access outside the
23///    collector.
24/// 3. The status of the object. This may change at any time.
25///
26/// If we decide an object is live, it is pretty safe to mark all of its children live since we
27/// don't do anything clever with buffering. The logic here is that the collector takes out a strong
28/// reference to the objects it traces, so any concurrent drops of objects during collection (making
29/// them dead) will put a weak reference into the young gen. If the object is acyclical, the [`Arc`]
30/// drop implementation will call drop on [`AtomicInner`] which will tear down its internal data
31/// properly. If the object is a node in a cyclical graph, it will retain at least one strong count
32/// which will allow the collector to upgrade its weak pointer and trace/collect the object.
33///
34/// If we decide an object is dead, we need to exercise caution when tearing down its data, since
35/// the child graph may have changed between the time we traced it and the time we're checking
36/// refcounts. If we know the child graph is unchanged, we can follow the normal rules for child
37/// cleanup (cleanup all children whose refcounts come from the dead graph). If it has changed, it's
38/// pretty safe to treat the object as still live, since _someone_ must have had a (possibly
39/// transitive) strong reference to it in order to alter the child graph, and so we'll at least have
40/// a weak reference in the old/young gen if it's been dropped since.
41///
42/// We track the dirty state of the child graph using [`Status::Traced`], which the collector sets
43/// as the state of any node it reaches during tracing. All operations which expose references
44/// overwrite this status (we can't rely on `&mut` due to interior mutability). This way, the
45/// collector can know that if it sees a dead object with the `Traced` status, both that object and
46/// its children are possibly dead. Conversely, if `Traced` has been overwritten, the object was
47/// definitely still alive.
48///
49/// In order to prevent races with altering child graphs during tracing, the collector acquires an
50/// exclusive lock prior to setting `Status::Traced`. This is to prevent races where a thread
51/// acquires a reference to an object's data prior to its traced status being set and subsequently
52/// modifies the graph through that reference after it's been set.
53mod collector;
54
55/// Contains the `sync` version of the [`Trace`] trait.
56pub mod trace;
57
58pub use collector::{
59    collect,
60    collect_full,
61    collect_with_options,
62    count_roots,
63    GcVisitor,
64};
65use collector::{
66    WeakNode,
67    YOUNG_GEN,
68};
69#[doc(inline)]
70pub use trace::Trace;
71
72/// Wraps a shared reference to a value in a [`Agc`].
73pub struct Ref<'a, T: ?Sized> {
74    guard: RwLockReadGuard<'a, ManuallyDrop<T>>,
75}
76
77impl<T: ?Sized> Deref for Ref<'_, T> {
78    type Target = T;
79
80    #[inline]
81    fn deref(&self) -> &T {
82        self.guard.deref().deref()
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit)]
87#[repr(i8)]
88enum Status {
89    /// The node has had its refcount incremented recently or a mutable/immutable borrow checked
90    /// out and is definitely alive.
91    Live,
92    /// The node had its reference count decremented recently and may be dead.
93    RecentlyDecremented,
94    /// The Collector visited this node during tracing, and the node has not been accessed since.
95    /// This state must be invalidated by any operation which returns a references (mutable or
96    /// otherwise), or the collector may prematurely destroy data.
97    ///
98    /// The particular area of concern is a graph that looks like:
99    /// - `Stack -> (Candidate) A -> B`
100    /// - `Thread A: Traverse A -> B`
101    /// - `Collector: Trace A, B`
102    /// - `Thread A: Attach B <- C, release A`
103    /// - `{ A (dead) \\ B <- C (Stack) }`
104    /// - `Collector: Check A, A is dead (strong = 1, count = 1) & B is dead (strong = 2, count =
105    ///   2).`
106    ///
107    /// If we don't know that A was possibly modified (and may have had its child graph modified),
108    /// we'll try to destroy B. By invalidating `Traced` on access to A's data, we know to remove B
109    /// from the list of `Dead` nodes.
110    Traced,
111    /// The Collector completed collection and believes the node is dead. This status is
112    /// unrecoverable.
113    Dead,
114}
115
116/// A cycle-collected reference-counted smart pointer which may be shared across threads and
117/// supports concurrent collection.
118///
119/// `Agc<T>` provides shared ownership of a value of type `T`, allocated on the heap. Cloning it
120/// will produce a new `Agc` instance which points to the same allocation as the original `Agc`.
121///
122/// Unlike [`std::sync::Arc`], `Agc` pointers may refer to each other in a way that creates cycles
123/// arbitrarily without causing leaks, provided that the program calls [`collect`] to collect those
124/// cycles.
125///
126/// - In most cases you **must** call collect to reclaim memory for dropped `Agc`s _even if they are
127///   acyclical_.
128/// - You may call [`collect`] from any thread to collect cycles. The implementation of collect is
129///   intended to provide very low pause times for concurrently running threads, although it will
130///   block the thread which actually invokes [`collect`] until the collection is complete.
131///     - While the exact details are subject to change, the current implementation will block
132///       access to `Agc`s data for as long as it takes to update its metadata, and will only do so
133///       if that `Agc` is a candidate for collection.
134pub struct Agc<T: Trace + 'static> {
135    ptr: Arc<AtomicInner<T>>,
136}
137
138impl<T> Agc<T>
139where
140    T: Trace + 'static,
141{
142    /// Construct a new `Agc` containing `data` which will be automatically cleaned up with it is no
143    /// longer reachable, even in the presence of cyclical references.
144    pub fn new(data: T) -> Self {
145        Self {
146            ptr: Arc::new(AtomicInner {
147                status: Atomic::new(Status::Live),
148                data: RwLock::new(ManuallyDrop::new(data)),
149            }),
150        }
151    }
152}
153
154impl<T> Agc<T>
155where
156    T: Trace + 'static,
157{
158    /// Retrieve the current number of strong references outstanding.
159    pub fn strong_count(this: &Self) -> usize {
160        Arc::strong_count(&this.ptr)
161    }
162
163    /// Blocks the thread until it can acquire a non-exclusive reference into this `Agc`.
164    ///
165    /// The returned object is an RAII guard which releases a shared lock. Multiple references may
166    /// be taken out at the same time.
167    ///
168    /// # Deadlocks
169    /// May deadlock if the value is `Dead`. This should not occur under normal circumstances and
170    /// likely indicates a bug in a [`Trace`] implementation.
171    ///
172    /// # Panics
173    /// Panics if the value is `Dead`. This should not occur under normal circumstances and likely
174    /// indicates a bug in a [`Trace`] implementation.
175    pub fn read(&self) -> Ref<'_, T> {
176        if self.ptr.status.load(atomic::Ordering::Acquire) != Status::Dead {
177            let guard = self.ptr.data.read_recursive();
178            // We must update this after acquiring the lock to prevent races with tracing and seeing
179            // nodes as possibly dirty.
180            self.ptr
181                .status
182                .store(Status::Live, atomic::Ordering::Release);
183            Ref { guard }
184        } else {
185            panic!("Attempted to acquire a reference to a dead object");
186        }
187    }
188
189    /// Try to get a non-exclusive reference into this `Agc`. This will not block the calling
190    /// thread. This may fail if the object is being concurrently traced by the collector.
191    ///
192    /// Returns None if the pointer is dead or exclusively referenced.
193    pub fn try_borrow(&self) -> Option<Ref<'_, T>> {
194        if self.ptr.status.load(atomic::Ordering::Acquire) != Status::Dead {
195            if let Some(guard) = self.ptr.data.try_read_recursive() {
196                // We must update this after acquiring the lock to prevent races with tracing and
197                // seeing nodes as possibly dirty.
198                self.ptr
199                    .status
200                    .store(Status::Live, atomic::Ordering::Release);
201                return Some(Ref { guard });
202            }
203        }
204
205        None
206    }
207
208    /// Returns true if both this & other point to the same allocation.
209    pub fn ptr_eq(this: &Self, other: &Self) -> bool {
210        Arc::ptr_eq(&this.ptr, &other.ptr)
211    }
212
213    fn node(&self) -> WeakNode {
214        let inner_ptr = Arc::downgrade(&self.ptr) as Weak<_>;
215        WeakNode { inner_ptr }
216    }
217}
218
219impl<T> Drop for Agc<T>
220where
221    T: Trace + 'static,
222{
223    fn drop(&mut self) {
224        // It is okay if this overwrites/is overwritten by Status::Traced.
225        // - It's fine if we overwrite Traced because we won't drop the inner data here (the
226        //   collector holds a strong reference), so we're not worried about child destructors
227        //   altering the object graph.
228        // - If the collector completes its tracing before this update, it will end up believing
229        //   this object is live anyways, since we haven't dropped the strong reference yet. Since
230        //   we are going to always store a weak reference in the young gen, we're not worried about
231        //   losing track of the object if it needs later cleanup.
232        //
233        // We're also okay with races with Status::Dead in the case of Trace bugs.
234        // - If the collector wins and sets Status::Dead, we won't overwrite it and will just do
235        //   nothing here.
236        // - If we win the race, the status will get overwritten with Dead and since we don't try to
237        //   read the data stored in this pointer, we don't end up deadlocking or panicking. The
238        //   leaked write lock prevents UB.
239        if self
240            .ptr
241            .status
242            .fetch_update(
243                atomic::Ordering::AcqRel,
244                atomic::Ordering::Relaxed,
245                |status| match status {
246                    Status::Dead => None,
247                    _ => Some(Status::RecentlyDecremented),
248                },
249            )
250            .is_err()
251        {
252            return;
253        }
254
255        let weak_node = self.node();
256        YOUNG_GEN.insert(weak_node, 0);
257    }
258}
259
260impl<T> Trace for Agc<T>
261where
262    T: Trace + 'static,
263{
264    fn visit_children(&self, visitor: &mut GcVisitor) {
265        visitor.visit_node(self)
266    }
267}
268
269impl<T> std::fmt::Debug for Agc<T>
270where
271    T: Trace + 'static,
272{
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        f.debug_struct("Agc")
275            .field("strong", &format_args!("{}", Self::strong_count(self)))
276            .field(
277                "data",
278                &format_args!(
279                    "{:?} @ {:?}",
280                    self.try_borrow().map(|_| std::any::type_name::<T>()),
281                    (&**self.ptr.data.read_recursive() as *const T)
282                ),
283            )
284            .finish()
285    }
286}
287
288impl<T> Clone for Agc<T>
289where
290    T: Trace + 'static,
291{
292    fn clone(&self) -> Self {
293        let _guard = self.ptr.data.read_recursive();
294        self.ptr
295            .status
296            .store(Status::Live, atomic::Ordering::Release);
297
298        Self {
299            ptr: self.ptr.clone(),
300        }
301    }
302}
303
304impl<T: Trace + 'static> PartialEq for Agc<T>
305where
306    T: PartialEq,
307{
308    fn eq(&self, other: &Self) -> bool {
309        *self.read() == *other.read()
310    }
311}
312
313impl<T: Trace + 'static> Eq for Agc<T> where T: Eq {}
314
315impl<T: Trace + 'static> PartialOrd for Agc<T>
316where
317    T: PartialOrd,
318{
319    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
320        self.read().partial_cmp(&other.read())
321    }
322}
323
324impl<T: Trace + 'static> Ord for Agc<T>
325where
326    T: Ord,
327{
328    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
329        self.read().cmp(&other.read())
330    }
331}
332
333pub(crate) struct AtomicInner<T: ?Sized + Trace> {
334    status: Atomic<Status>,
335    data: RwLock<ManuallyDrop<T>>,
336}
337
338impl<T> Drop for AtomicInner<T>
339where
340    T: ?Sized + Trace,
341{
342    fn drop(&mut self) {
343        self.drop_data();
344    }
345}
346
347impl<T> std::fmt::Debug for AtomicInner<T>
348where
349    T: ?Sized + Trace,
350{
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        f.debug_struct("Inner")
353            .field("status", &self.status)
354            .field(
355                "data",
356                &format_args!(
357                    "{} @ {:?}",
358                    std::any::type_name::<T>(),
359                    std::ptr::addr_of!(self.data)
360                ),
361            )
362            .finish()
363    }
364}
365
366impl<T> AtomicInner<T>
367where
368    T: ?Sized + Trace,
369{
370    fn drop_data(&self) {
371        if let Some(mut data_guard) = self.data.try_write() {
372            self.status.store(Status::Dead, atomic::Ordering::Release);
373
374            unsafe { ManuallyDrop::drop(&mut data_guard) };
375
376            // This is only okay because we're using `parking_lot` mutex. If this is a
377            // `std::sync::RwLock` it will cause undefined behavior or leak.
378            //
379            // https://github.com/rust-lang/rust/issues/85434
380            std::mem::forget(data_guard);
381        }
382    }
383}