tracing_rc/rc/
collector.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    num::NonZeroUsize,
5    rc::{
6        Rc,
7        Weak,
8    },
9};
10
11use indexmap::{
12    IndexMap,
13    IndexSet,
14};
15use petgraph::{
16    csr::{
17        Csr,
18        NodeIndex,
19    },
20    Directed,
21};
22
23use crate::{
24    impl_node,
25    rc::{
26        trace::Trace,
27        Gc,
28        Inner,
29        Status,
30    },
31    CollectOptions,
32    CollectionType,
33};
34
35impl_node!(WeakNode { inner_ptr: Weak<Inner<dyn Trace>> }, upgrade(ptr) => Weak::upgrade(ptr));
36impl_node!(StrongNode { inner_ptr: Rc<Inner<dyn Trace>> }, upgrade(ptr) => ptr);
37
38impl TryFrom<WeakNode> for StrongNode {
39    type Error = ();
40
41    fn try_from(weak: WeakNode) -> Result<Self, Self::Error> {
42        weak.upgrade()
43            .map(|strong| StrongNode { inner_ptr: strong })
44            .ok_or(())
45    }
46}
47
48/// Visitor provided during tracing of the reachable object graph. You shouldn't need to interact
49/// with this as [`Gc::visit_children`] will do the right thing for you.
50pub struct GcVisitor<'cycle> {
51    visitor: &'cycle mut dyn FnMut(Rc<Inner<dyn Trace>>),
52}
53
54impl GcVisitor<'_> {
55    /// Visit an owned [`Gc`] node.
56    pub fn visit_node<T: Trace + 'static>(&mut self, node: &Gc<T>) {
57        (self.visitor)(node.ptr.clone() as Rc<_>);
58    }
59}
60
61type GraphIndex = NodeIndex<usize>;
62
63type ConnectivityGraph = Csr<(), (), Directed, GraphIndex>;
64
65type TracedNodeList = IndexMap<StrongNode, NonZeroUsize>;
66
67thread_local! { pub(super) static OLD_GEN: RefCell<IndexSet<WeakNode>> = RefCell::new(IndexSet::default()) }
68thread_local! { pub(super) static YOUNG_GEN: RefCell<IndexMap<WeakNode, usize>> = RefCell::new(IndexMap::default()) }
69
70#[doc(hidden)]
71pub fn count_roots() -> usize {
72    OLD_GEN.with(|old| old.borrow().len()) + YOUNG_GEN.with(|young| young.borrow().len())
73}
74
75/// Perform a full, cycle-tracing collection of both the old & young gen.
76pub fn collect_full() {
77    collect_with_options(CollectOptions::TRACE_AND_COLLECT_ALL);
78}
79
80/// Perform a normal collection cycle.
81pub fn collect() {
82    collect_with_options(CollectOptions::default());
83}
84
85/// Perform a collection cycle based on [`CollectOptions`].
86pub fn collect_with_options(options: CollectOptions) {
87    collect_new_gen(options);
88    if options.kind != CollectionType::YoungOnly {
89        collect_old_gen();
90    }
91}
92
93fn collect_new_gen(options: CollectOptions) {
94    OLD_GEN.with(|old_gen| {
95        let mut old_gen = old_gen.borrow_mut();
96        YOUNG_GEN.with(|gen| {
97            gen.borrow_mut().retain(|node, generation| {
98                let strong_node = if let Some(node) = node.upgrade() {
99                    node
100                } else {
101                    return false;
102                };
103                if strong_node.status.get() != Status::RecentlyDecremented {
104                    // It is alive or dead, either way we won't need to trace its children.
105                    // If it's alive, we'll get another chance to clean it up.
106                    // If it's dead, it can't have children so the destructor should handle
107                    // cleanup eventually.
108                    return false;
109                }
110
111                if *generation < options.old_gen_threshold {
112                    *generation += 1;
113                    return true;
114                }
115
116                old_gen.insert(node.clone());
117                false
118            });
119        });
120    });
121}
122
123fn collect_old_gen() {
124    let mut traced_nodes: TracedNodeList = OLD_GEN.with(|old_gen| {
125        old_gen
126            .borrow_mut()
127            .drain(..)
128            .filter_map(|weak| {
129                weak.try_into()
130                    .ok()
131                    .map(|strong| (strong, NonZeroUsize::new(1).unwrap()))
132            })
133            .collect()
134    });
135
136    let mut connectivity_graph = ConnectivityGraph::with_nodes(traced_nodes.len());
137
138    // Iterate over all nodes reachable from the old gen tracking them in the list of all
139    // traced nodes.
140    // We iterate over the initial list of nodes here, since all new nodes are added afterwards.
141    for node_ix in 0..connectivity_graph.node_count() {
142        match traced_nodes.get_index(node_ix).unwrap().0.status.get() {
143            Status::Live | Status::Dead => {
144                // We'll collect it later if it a new strong reference was created and added
145                // to a now dead cycle before being collected. We don't really have any work
146                // to do here otherwise.
147                // Dead nodes can be here if a buggy trace implementation or bad drop behavior
148                // caused a node to be improperly cleaned up.
149            }
150            Status::RecentlyDecremented => {
151                // This node had a strong ref dropped recently and might form a cycle, trace
152                // it
153                trace_children(node_ix, &mut traced_nodes, &mut connectivity_graph);
154            }
155        }
156    }
157
158    let mut live_nodes = vec![];
159    let mut dead_nodes = HashMap::default();
160
161    for (node_ix, (node, refs)) in traced_nodes.into_iter().enumerate() {
162        if Rc::strong_count(&node) > refs.get() {
163            live_nodes.push(node_ix);
164        } else {
165            dead_nodes.insert(node_ix, node);
166        }
167    }
168
169    for node_index in live_nodes {
170        filter_live_node_children(&connectivity_graph, node_index, &mut dead_nodes);
171    }
172
173    for node in dead_nodes.values() {
174        node.status.set(Status::Dead);
175    }
176
177    for (_, node) in dead_nodes {
178        node.drop_data();
179    }
180}
181
182fn trace_children(
183    parent_ix: GraphIndex,
184    traced_nodes: &mut TracedNodeList,
185    connectivity_graph: &mut ConnectivityGraph,
186) {
187    let pin_parent = traced_nodes.get_index(parent_ix).unwrap().0.clone();
188    let parent = if let Ok(parent) = pin_parent.data.try_borrow_mut() {
189        parent
190    } else {
191        // If we can't get mutable access to child, it must be exclusively borrowed by somebody
192        // somewhere on the stack or from within this trace implementation.
193        //
194        // If it's our borrow, we're already visiting that nodes children, so there's no reason for
195        // us to do so again.
196        //
197        // If it's someone else, it must be live and not visiting its
198        // children will undercount any nodes it owns, which is guaranteed to prevent us
199        // from deciding those nodes are dead.
200        return;
201    };
202
203    parent.visit_children(&mut GcVisitor {
204        visitor: &mut |node| {
205            match traced_nodes.entry(node.into()) {
206                indexmap::map::Entry::Occupied(mut seen) => {
207                    // We've already seen this node. We do a saturating add because it's ok to
208                    // undercount references here and usize::max references is kind of a degenerate
209                    // case.
210                    *seen.get_mut() =
211                        NonZeroUsize::new(seen.get().get().saturating_add(1)).unwrap();
212
213                    connectivity_graph.add_edge(parent_ix, seen.index(), ());
214                }
215                indexmap::map::Entry::Vacant(unseen) => {
216                    let child_ix = connectivity_graph.add_node(());
217                    debug_assert_eq!(child_ix, unseen.index());
218
219                    connectivity_graph.add_edge(parent_ix, child_ix, ());
220
221                    // 1 for the graph strong reference, 1 for the seen reference.
222                    unseen.insert(NonZeroUsize::new(2).unwrap());
223                    trace_children(child_ix, traced_nodes, connectivity_graph);
224                }
225            };
226        },
227    });
228}
229
230fn filter_live_node_children(
231    graph: &ConnectivityGraph,
232    node: GraphIndex,
233    dead_nodes: &mut HashMap<GraphIndex, StrongNode>,
234) {
235    for child in graph.neighbors_slice(node) {
236        if dead_nodes.remove(child).is_some() {
237            filter_live_node_children(graph, *child, dead_nodes);
238        }
239    }
240}