atomic.rs |
|
55387 |
collector.rs |
|
12179 |
default.rs |
The default garbage collector.
For each thread, a participant is lazily initialized on its first use, when the current thread
is registered in the default collector. If initialized, the thread's participant will get
destructed on thread exit, which in turn unregisters the thread. |
2501 |
deferred.rs |
|
4053 |
epoch.rs |
The global epoch
The last bit in this number is unused and is always zero. Every so often the global epoch is
incremented, i.e. we say it "advances". A pinned participant may advance the global epoch only
if all currently pinned participants have been pinned in the current epoch.
If an object became garbage in some epoch, then we can be sure that after two advancements no
participant will hold a reference to it. That is the crux of safe memory reclamation. |
4751 |
guard.rs |
|
19403 |
internal.rs |
The global data and participant for garbage collection.
# Registration
In order to track all participants in one place, we need some form of participant
registration. When a participant is created, it is registered to a global lock-free
singly-linked list of registries; and when a participant is leaving, it is unregistered from the
list.
# Pinning
Every participant contains an integer that tells whether the participant is pinned and if so,
what was the global epoch at the time it was pinned. Participants also hold a pin counter that
aids in periodic global epoch advancement.
When a participant is pinned, a `Guard` is returned as a witness that the participant is pinned.
Guards are necessary for performing atomic operations, and for freeing/dropping locations.
# Thread-local bag
Objects that get unlinked from concurrent data structures must be stashed away until the global
epoch sufficiently advances so that they become safe for destruction. Pointers to such objects
are pushed into a thread-local bag, and when it becomes full, the bag is marked with the current
global epoch and pushed into the global queue of bags. We store objects in thread-local storages
for amortizing the synchronization cost of pushing the garbages to a global queue.
# Global queue
Whenever a bag is pushed into a queue, the objects in some bags in the queue are collected and
destroyed along the way. This design reduces contention on data structures. The global queue
cannot be explicitly accessed: the only way to interact with it is by calling functions
`defer()` that adds an object to the thread-local bag, or `collect()` that manually triggers
garbage collection.
Ideally each instance of concurrent data structure may have its own queue that gets fully
destroyed as soon as the data structure gets dropped. |
21497 |
lib.rs |
Epoch-based memory reclamation.
An interesting problem concurrent collections deal with comes from the remove operation.
Suppose that a thread removes an element from a lock-free map, while another thread is reading
that same element at the same time. The first thread must wait until the second thread stops
reading the element. Only then it is safe to destruct it.
Programming languages that come with garbage collectors solve this problem trivially. The
garbage collector will destruct the removed element when no thread can hold a reference to it
anymore.
This crate implements a basic memory reclamation mechanism, which is based on epochs. When an
element gets removed from a concurrent collection, it is inserted into a pile of garbage and
marked with the current epoch. Every time a thread accesses a collection, it checks the current
epoch, attempts to increment it, and destructs some garbage that became so old that no thread
can be referencing it anymore.
That is the general mechanism behind epoch-based memory reclamation, but the details are a bit
more complicated. Anyhow, memory reclamation is designed to be fully automatic and something
users of concurrent collections don't have to worry much about.
# Pointers
Concurrent collections are built using atomic pointers. This module provides [`Atomic`], which
is just a shared atomic pointer to a heap-allocated object. Loading an [`Atomic`] yields a
[`Shared`], which is an epoch-protected pointer through which the loaded object can be safely
read.
# Pinning
Before an [`Atomic`] can be loaded, a participant must be [`pin`]ned. By pinning a participant
we declare that any object that gets removed from now on must not be destructed just
yet. Garbage collection of newly removed objects is suspended until the participant gets
unpinned.
# Garbage
Objects that get removed from concurrent collections must be stashed away until all currently
pinned participants get unpinned. Such objects can be stored into a thread-local or global
storage, where they are kept until the right time for their destruction comes.
There is a global shared instance of garbage queue. You can [`defer`](Guard::defer) the execution of an
arbitrary function until the global epoch is advanced enough. Most notably, concurrent data
structures may defer the deallocation of an object.
# APIs
For majority of use cases, just use the default garbage collector by invoking [`pin`]. If you
want to create your own garbage collector, use the [`Collector`] API. |
6331 |
sync |
|
|