-
Notifications
You must be signed in to change notification settings - Fork 16
/
collect.rs
357 lines (332 loc) · 11.9 KB
/
collect.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT
// or http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use core::cell::RefCell;
use core::ptr::NonNull;
use crate::cc_box_ptr::{free, CcBoxPtr};
use crate::Color;
thread_local!(static ROOTS: RefCell<Vec<NonNull<dyn CcBoxPtr>>> = RefCell::new(vec![]));
#[doc(hidden)]
pub fn add_root(box_ptr: NonNull<dyn CcBoxPtr>) {
ROOTS.with(|r| {
let mut vec = r.borrow_mut();
vec.push(box_ptr);
});
}
/// Return the number of potential cycle roots currently buffered for cycle
/// collection.
///
/// Whenever a `Cc<T>`'s reference count is decremented, it has the possibility
/// of becoming the root of some cycle that is no longer live and can now be
/// reclaimed. These possible roots are buffered for cycle detection and
/// collection at a later point in time. This enables library users to avoid
/// frequent tracing and perform that tracing at a convenient time. Part of
/// choosing a convenient time might be when the number of potential cycle roots
/// reaches some critical threshold. This method allows you to check the current
/// number of possible roots buffered.
///
/// ```rust
/// use bacon_rajan_cc::{Cc, Trace, Tracer, number_of_roots_buffered};
/// use std::cell::RefCell;
///
/// struct Gadget {
/// parent: Option<Cc<RefCell<Gadget>>>,
/// children: Vec<Cc<RefCell<Gadget>>>,
/// // ...
/// }
///
/// impl Trace for Gadget {
/// fn trace(&self, tracer: &mut Tracer) {
/// self.parent.trace(tracer);
/// self.children.trace(tracer);
/// }
/// }
///
/// fn add_child(parent: &mut Cc<RefCell<Gadget>>) -> Cc<RefCell<Gadget>> {
/// let child = Cc::new(RefCell::new(Gadget {
/// parent: None,
/// children: vec![],
/// }));
/// child.borrow_mut().parent = Some(parent.clone());
/// parent.borrow_mut().children.push(child.clone());
/// child
/// }
///
/// pub fn main() {
/// // No possible roots, as we haven't created any `Cc<T>`s yet.
/// assert_eq!(number_of_roots_buffered(), 0);
///
/// {
/// let mut parent = Cc::new(RefCell::new(Gadget {
/// parent: None,
/// children: vec![],
/// }));
/// let mut children = vec![];
/// for _ in 0..10 {
/// children.push(add_child(&mut parent));
/// }
///
/// // No possible roots, we have only incremented reference counts and
/// // created new `Cc<T>`s. We have not decremented any reference
/// // counts or created any dead cycles.
/// assert_eq!(number_of_roots_buffered(), 0);
/// }
///
/// // None of the Gadgets are reachable anymore, but they had cyclic
/// // references between parents and children. However, because their
/// // reference counts were decremented when we left the block, they should
/// // be buffered for cycle collection.
/// assert_eq!(
/// number_of_roots_buffered(),
/// 1 /* parent */ + 10 /* children */
/// );
///
/// // reclaim the cycle
/// bacon_rajan_cc::collect_cycles();
/// assert_eq!(number_of_roots_buffered(), 0);
/// }
/// ```
pub fn number_of_roots_buffered() -> usize {
ROOTS.with(|r| r.borrow().len())
}
/// Invoke cycle collection for all `Cc<T>`s on this thread.
///
/// You may wish to do this when the roots buffer reaches a certain size, when
/// memory is low, or at opportune moments within your application (such as when
/// the user has been inactive for `n` seconds in a GUI application).
///
/// This happens in three phases:
///
/// 1. `mark_roots`: We mark the roots and decrement reference counts as we
/// go. This is optimistically removing the strong references held by the
/// potentially dead cycles.
///
/// 2. `scan_roots`: Then we perform a second traversal which marks the garbage
/// nodes with a reference count of 0 as White and the non-garbage nodes with a
/// reference count > 0 as Black. The latter group's reference count is restored
/// to its previous value from before step (1).
///
/// 3. `collect_roots`: Finally, the buffer of possible dead cycle roots is
/// emptied and members of dead cycles (White nodes) are dropped.
///
/// ```rust
/// use bacon_rajan_cc::{Cc, Trace, Tracer, collect_cycles};
/// use std::cell::RefCell;
///
/// // The number of Gadgets allocated at any given time.
/// thread_local!(static GADGET_COUNT: RefCell<usize> = RefCell::new(0));
///
/// struct Gadget {
/// parent: Option<Cc<RefCell<Gadget>>>,
/// children: Vec<Cc<RefCell<Gadget>>>,
/// // ...
/// }
///
/// impl Gadget {
/// fn new() -> Gadget {
/// GADGET_COUNT.with(|c| *c.borrow_mut() += 1);
/// Gadget { parent: None, children: vec!() }
/// }
/// }
///
/// impl Trace for Gadget {
/// fn trace(&self, tracer: &mut Tracer) {
/// self.parent.trace(tracer);
/// self.children.trace(tracer);
/// }
/// }
///
/// impl Drop for Gadget {
/// fn drop(&mut self) {
/// GADGET_COUNT.with(|c| *c.borrow_mut() -= 1);
/// }
/// }
///
/// fn add_child(parent: &mut Cc<RefCell<Gadget>>) -> Cc<RefCell<Gadget>> {
/// let child = Cc::new(RefCell::new(Gadget::new()));
/// child.borrow_mut().parent = Some(parent.clone());
/// parent.borrow_mut().children.push(child.clone());
/// child
/// }
///
/// pub fn main() {
/// // Initially, no gadgets.
/// GADGET_COUNT.with(|c| assert_eq!(*c.borrow(), 0));
///
/// {
/// // Create cycles.
///
/// let mut parent = Cc::new(RefCell::new(Gadget::new()));
/// for _ in 0..10 {
/// add_child(&mut parent);
/// }
///
/// // We created 1 parent and 10 child gadgets.
/// GADGET_COUNT.with(|c| assert_eq!(*c.borrow(), 11));
/// }
///
/// // The members of the cycle are now dead, but because of the cycles
/// // could not be eagerly collected.
/// GADGET_COUNT.with(|c| assert_eq!(*c.borrow(), 11));
///
/// // After calling `collect_cycles`, the cycles are detected and the
/// // members of the dead cycles are dropped.
/// collect_cycles();
/// GADGET_COUNT.with(|c| assert_eq!(*c.borrow(), 0));
/// }
/// ```
pub fn collect_cycles() {
mark_roots();
scan_roots();
collect_roots();
}
/// Consider every node that's been stored in the buffer since the last
/// collection. If the node is Purple, then the last operation on it was a
/// decrement of its reference count, and it hasn't been touched since then. It
/// is potentially the root of a garbage cycle. Perform a graph traversal and
/// optimistically decrement reference counts as we go. At the end of the
/// traversal, anything whose reference count became 0 was part of a garbage
/// cycle. Anything whose reference count did not become 0 was not part of a
/// garbage cycle, and we will have to restore its old reference count in
/// `scan_roots`.
fn mark_roots() {
fn mark_gray(cc_box_ptr: &dyn CcBoxPtr) {
if cc_box_ptr.data().color() == Color::Gray {
return;
}
cc_box_ptr.data().color.set(Color::Gray);
cc_box_ptr.trace(&mut |t| {
let t = unsafe { t.as_ref() };
t.data().dec_strong();
mark_gray(t);
});
}
let old_roots: Vec<_> = ROOTS.with(|r| {
let mut v = r.borrow_mut();
let drained = v.drain(..);
drained.collect()
});
let mut new_roots : Vec<_> = old_roots.into_iter().filter_map(|s| {
let keep = unsafe {
let box_ptr : &dyn CcBoxPtr = s.as_ref();
if box_ptr.data().color() == Color::Purple {
mark_gray(box_ptr);
true
} else {
box_ptr.data().buffered.set(false);
if box_ptr.data().color() == Color::Black && box_ptr.data().strong() == 0 {
free(s);
}
false
}
};
if keep {
Some(s)
} else {
None
}
}).collect();
ROOTS.with(|r| {
let mut v = r.borrow_mut();
v.append(&mut new_roots);
});
}
/// This is the second traversal, after marking. Color each node in the graph as
/// White nodes if its reference count is 0 and it is part of a garbage cycle,
/// or Black if the node is still live.
fn scan_roots() {
fn scan_black(s: &dyn CcBoxPtr) {
s.data().color.set(Color::Black);
s.trace(&mut |t| {
let t = unsafe { t.as_ref() };
t.data().strong.set(t.data().strong() + 1);
if t.data().color() != Color::Black {
scan_black(t);
}
});
}
fn scan(s: &dyn CcBoxPtr) {
if s.data().color() != Color::Gray {
return;
}
if s.data().strong() > 0 {
scan_black(s);
} else {
s.data().color.set(Color::White);
s.trace(&mut |t| {
scan(unsafe { t.as_ref() });
});
}
}
ROOTS.with(|r| {
let mut v = r.borrow_mut();
for s in &mut *v {
scan(unsafe { s.as_ref() });
}
});
}
/// Go through all the White roots and their garbage cycles and collect these nodes.
/// If a White node is still in the roots buffer, then leave it
/// there. It will be freed in the next collection when we iterate over the
/// buffer in `mark_roots`.
fn collect_roots() {
// Collecting the nodes into this Vec is a difference from the original
// Bacon-Rajan paper. We need this because we have destructors and
// running them during traversal will cause cycles to be broken which
// ruins the rest of our traversal.
let mut white = Vec::new();
fn collect_white(ptr: NonNull<dyn CcBoxPtr>, white: &mut Vec<NonNull<dyn CcBoxPtr>>) {
let s = unsafe { ptr.as_ref() };
if s.data().color() == Color::White && !s.data().buffered() {
s.data().color.set(Color::Black);
s.trace(&mut |t| {
collect_white(t, white);
});
s.data().inc_weak();
white.push(ptr);
}
}
ROOTS.with(|r| {
let mut v = r.borrow_mut();
for ptr in v.drain(..) {
let s: &dyn CcBoxPtr = unsafe { ptr.as_ref() };
s.data().buffered.set(false);
collect_white(ptr, &mut white);
}
});
// Run drop on each of nodes. The previous increment of the weak count during traversal will
// ensure that all of the memory stays alive during this loop.
for i in &white {
// Calling drop() will decrement the reference count on any of our live children.
// However, during trial deletion the reference count was already decremented
// so we'll end up decrementing twice. To avoid that, we increment the count
// just before calling drop() so that it balances out. This is another difference
// from the original paper caused by having destructors that we need to run.
let ptr: &dyn CcBoxPtr = unsafe { i.as_ref() };
ptr.trace(&mut |t| {
let t = unsafe { t.as_ref() };
if t.data().strong() > 0 {
t.data().strong.set(t.data().strong() + 1)
}
});
unsafe { crate::drop_value(*i) };
unsafe { free(*i) };
}
// It's now safe to deallocate the memory as long as we are the last weak reference.
for i in &white {
unsafe {
// Only deallocate if our weak reference is the only one.
if i.as_ref().data().weak() == 1 {
crate::deallocate(*i);
} else {
// undo s.inc_weak() from collect_white
i.as_ref().data().dec_weak();
}
}
}
}