Feel free to open issues or pull requests. Feature requests can be done opening an issue, the enhancement tag will be applied by maintainers.
For pull requests, use rustftm to format your files and make sure to
allow edits by maintainers.
Also, remember to open the pull requests toward the dev
branch. The main
branch is only for releases!
The core idea behind the algorithm is the same as the one presented by Lins in "Cyclic Reference Counting With Lazy Mark-Scan"
and by Bacon and Rajan in "Concurrent Cycle Collection in Reference Counted Systems".
However, the implementation differs in order to make the collector faster and more resilient to panics and failures.
Important
rust-cc
is not strictly an implementation of the algorithm shown in the linked papers and it's never been
intended as such. Feel free to propose any kind of improvement!
The following is a summarized version of the collection algorithm:
When a Cc
smart pointer is dropped, the reference count (RC) is decreased by 1. If it reaches 0, then the allocated
object pointed by the Cc
(called CcBox
) is dropped, otherwise the Cc
is put into the POSSIBLE_CYCLES
list.
The POSSIBLE_CYCLES
is an (intrusive) list which contains the possible roots of cyclic garbage.
Sometimes (see crate::trigger_collection
), when creating a new Cc
or when collect_cycles
is called,
the objects inside the POSSIBLE_CYCLES
list are checked to see if they are part of a garbage cycle.
Therefore, they undergo two tracing phases:
- Trace Counting: during this phase, a breadth-first traversal of the heap is performed starting from the elements inside
POSSIBLE_CYCLES
to count the number of pointers to eachCcBox
that is reachable from the list'sCc
s.
Thetracing_counter
"field" (see thecounter_marker
module for more info) is used to keep track of this number.About tracing_counter
In the papers, Lins, Bacon and Rajan decrement the RC itself instead of using another counter. However, if during tracing there was a panic, it would be hard for `rust-cc` to restore the RC correctly. This is the reason for the choice of having another counter. The invariant regarding this second counter is that it must always be between 0 and RC (inclusively).
- Trace Roots: now, every
CcBox
which has the RC strictly greater thantracing_counter
can be considered a root, since it must exist aCc
pointer which points at it that hasn't been traced in the previous phase. Thus, a trace is started from these roots, and all objects not reached during this trace are finalized/deallocated (this is actually more complex due to possible object resurrections, see the comments in lib.rs).
Note that this second phase is correct only if the graph formed by the pointers is not changed between the two phases. Thus, this is a key requirement of theTrace
trait and one of the reasons it is markedunsafe
.
When debug assertions are enabled, the add
method may panic before actually updating the list to contain the added object.
Similarly, the remove
method may panic after having removed the object from the list.
Thus, marking should be done only:
- after the call to the
add
method - before the call to the
remove
method.
Every unit test (which makes use of a part of the collector) should start with a call to tests::reset_state()
to make
sure errors in other tests don't impact the current one.
Docs are always built with every feature enabled. This makes it easier to write and maintain the documentation.
However, this also makes it more difficult to write examples, as those must pass CI even when a features they require is disabled.
As such, examples are marked as ignore
d if a feature they need is missing:
#![cfg_attr(
feature = "derive",
doc = r"```rust"
)]
#![cfg_attr(
not(feature = "derive"),
doc = r"```rust,ignore"
)]
#![doc = r"# use rust_cc::*;
# use rust_cc_derive::*;
// Example code
```"]