Coverage Report

Created: 2026-07-13 08:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/seize-0.5.1/src/collector.rs
Line
Count
Source
1
use crate::raw::{self, membarrier, Thread};
2
use crate::{LocalGuard, OwnedGuard};
3
4
use std::fmt;
5
use std::sync::OnceLock;
6
7
/// A concurrent garbage collector.
8
///
9
/// A `Collector` manages the access and retirement of concurrent objects
10
/// Objects can be safely loaded through *guards*, which can be created using
11
/// the [`enter`](Collector::enter) or [`enter_owned`](Collector::enter_owned)
12
/// methods.
13
///
14
/// Every instance of a concurrent data structure should typically own its
15
/// `Collector`. This allows the garbage collection of non-`'static` values, as
16
/// memory reclamation is guaranteed to run when the `Collector` is dropped.
17
#[repr(transparent)]
18
pub struct Collector {
19
    /// The underlying raw collector instance.
20
    pub(crate) raw: raw::Collector,
21
}
22
23
impl Default for Collector {
24
20.6k
    fn default() -> Self {
25
20.6k
        Self::new()
26
20.6k
    }
27
}
28
29
impl Collector {
30
    /// The default batch size for a new collector.
31
    const DEFAULT_BATCH_SIZE: usize = 32;
32
33
    /// Creates a new collector.
34
20.6k
    pub fn new() -> Self {
35
        // Initialize the `membarrier` module, detecting the presence of
36
        // operating-system strong barrier APIs.
37
20.6k
        membarrier::detect();
38
39
        // available_parallelism is quite slow (microseconds).
40
        static CPUS: OnceLock<usize> = OnceLock::new();
41
20.6k
        let cpus = *CPUS.get_or_init(|| {
42
2
            std::thread::available_parallelism()
43
2
                .map(Into::into)
44
2
                .unwrap_or(1)
45
2
        });
46
47
        // Ensure every batch accumulates at least as many entries
48
        // as there are threads on the system.
49
20.6k
        let batch_size = cpus.max(Self::DEFAULT_BATCH_SIZE);
50
51
20.6k
        Self {
52
20.6k
            raw: raw::Collector::new(cpus, batch_size),
53
20.6k
        }
54
20.6k
    }
55
56
    /// Sets the number of objects that must be in a batch before reclamation is
57
    /// attempted.
58
    ///
59
    /// Retired objects are added to thread-local *batches* before starting the
60
    /// reclamation process. After `batch_size` is hit, the objects are moved to
61
    /// separate *retirement lists*, where reference counting kicks in and
62
    /// batches are eventually reclaimed.
63
    ///
64
    /// A larger batch size amortizes the cost of retirement. However,
65
    /// reclamation latency can also grow due to the large number of objects
66
    /// needed to be freed. Note that reclamation can not be attempted
67
    /// unless the batch contains at least as many objects as the number of
68
    /// active threads.
69
    ///
70
    /// The default batch size is `32`.
71
0
    pub fn batch_size(mut self, batch_size: usize) -> Self {
72
0
        self.raw.batch_size = batch_size;
73
0
        self
74
0
    }
75
76
    /// Marks the current thread as active, returning a guard that protects
77
    /// loads of concurrent objects for its lifetime. The thread will be
78
    /// marked as inactive when the guard is dropped.
79
    ///
80
    /// Note that loads of objects that may be retired must be protected with
81
    /// the [`Guard::protect`]. See [the
82
    /// guide](crate::guide#starting-operations) for an introduction to
83
    /// using guards, or the documentation of [`LocalGuard`] for
84
    /// more details.
85
    ///
86
    /// Note that `enter` is reentrant, and it is legal to create multiple
87
    /// guards on the same thread. The thread will stay marked as active
88
    /// until the last guard is dropped.
89
    ///
90
    /// [`Guard::protect`]: crate::Guard::protect
91
    ///
92
    /// # Performance
93
    ///
94
    /// Performance-wise, creating and destroying a `LocalGuard` is about the
95
    /// same as locking and unlocking an uncontended `Mutex`. Because of
96
    /// this, guards should be reused across multiple operations if
97
    /// possible. However, holding a guard prevents the reclamation of any
98
    /// concurrent objects retired during its lifetime, so there is
99
    /// a tradeoff between performance and memory usage.
100
    ///
101
    /// # Examples
102
    ///
103
    /// ```rust
104
    /// # use std::sync::atomic::{AtomicPtr, Ordering};
105
    /// use seize::Guard;
106
    /// # let collector = seize::Collector::new();
107
    ///  
108
    /// // An atomic object.
109
    /// let ptr = AtomicPtr::new(Box::into_raw(Box::new(1_usize)));
110
    ///
111
    /// {
112
    ///     // Create a guard that is active for this scope.
113
    ///     let guard = collector.enter();
114
    ///
115
    ///     // Read the object using a protected load.
116
    ///     let value = guard.protect(&ptr, Ordering::Acquire);
117
    ///     unsafe { assert_eq!(*value, 1) }
118
    ///
119
    ///     // If there are other thread that may retire the object,
120
    ///     // the pointer is no longer valid after the guard is dropped.
121
    ///     drop(guard);
122
    /// }
123
    /// # unsafe { drop(Box::from_raw(ptr.load(Ordering::Relaxed))) };
124
    /// ```
125
    #[inline]
126
380k
    pub fn enter(&self) -> LocalGuard<'_> {
127
380k
        LocalGuard::enter(self)
128
380k
    }
<seize::collector::Collector>::enter
Line
Count
Source
126
380k
    pub fn enter(&self) -> LocalGuard<'_> {
127
380k
        LocalGuard::enter(self)
128
380k
    }
Unexecuted instantiation: <seize::collector::Collector>::enter
129
130
    /// Create an owned guard that protects objects for its lifetime.
131
    ///
132
    /// Unlike local guards created with [`enter`](Collector::enter), owned
133
    /// guards are independent of the current thread, allowing them to
134
    /// implement `Send` and `Sync`. See the documentation of [`OwnedGuard`]
135
    /// for more details.
136
    #[inline]
137
0
    pub fn enter_owned(&self) -> OwnedGuard<'_> {
138
0
        OwnedGuard::enter(self)
139
0
    }
140
141
    /// Retires a value, running `reclaim` when no threads hold a reference to
142
    /// it.
143
    ///
144
    /// Note that this method is disconnected from any guards on the current
145
    /// thread, so the pointer may be reclaimed immediately. Use
146
    /// [`Guard::defer_retire`](crate::Guard::defer_retire) if the pointer may
147
    /// still be accessed by the current thread while the guard is active.
148
    ///
149
    /// # Safety
150
    ///
151
    /// The retired pointer must no longer be accessible to any thread that
152
    /// enters after it is removed. It also cannot be accessed by the
153
    /// current thread after `retire` is called.
154
    ///
155
    /// Additionally, the pointer must be valid to pass to the provided
156
    /// reclaimer, once it is safe to reclaim.
157
    ///
158
    /// # Examples
159
    ///
160
    /// Common reclaimers are provided by the [`reclaim`](crate::reclaim)
161
    /// module.
162
    ///
163
    /// ```
164
    /// # use std::sync::atomic::{AtomicPtr, Ordering};
165
    /// # let collector = seize::Collector::new();
166
    /// use seize::reclaim;
167
    ///
168
    /// // An atomic object.
169
    /// let ptr = AtomicPtr::new(Box::into_raw(Box::new(1_usize)));
170
    ///
171
    /// // Create a guard.
172
    /// let guard = collector.enter();
173
    ///
174
    /// // Store a new value.
175
    /// let old = ptr.swap(Box::into_raw(Box::new(2_usize)), Ordering::Release);
176
    ///
177
    /// // Reclaim the old value.
178
    /// //
179
    /// // Safety: The `swap` above made the old value unreachable for any new threads.
180
    /// // Additionally, the old value was allocated with a `Box`, so `reclaim::boxed`
181
    /// // is valid.
182
    /// unsafe { collector.retire(old, reclaim::boxed) };
183
    /// # unsafe { collector.retire(ptr.load(Ordering::Relaxed), reclaim::boxed) };
184
    /// ```
185
    ///
186
    /// Alternative, a custom reclaimer function can be used.
187
    ///
188
    /// ```
189
    /// use seize::Collector;
190
    ///
191
    /// let collector = Collector::new();
192
    ///
193
    /// // Allocate a value and immediately retire it.
194
    /// let value: *mut usize = Box::into_raw(Box::new(1_usize));
195
    ///
196
    /// // Safety: The value was never shared.
197
    /// unsafe {
198
    ///     collector.retire(value, |ptr: *mut usize, _collector: &Collector| unsafe {
199
    ///         // Safety: The value was allocated with `Box::new`.
200
    ///         let value = Box::from_raw(ptr);
201
    ///         println!("Dropping {value}");
202
    ///         drop(value);
203
    ///     });
204
    /// }
205
    /// ```
206
    #[inline]
207
0
    pub unsafe fn retire<T>(&self, ptr: *mut T, reclaim: unsafe fn(*mut T, &Collector)) {
208
0
        debug_assert!(!ptr.is_null(), "attempted to retire a null pointer");
209
210
        // Note that `add` doesn't ever actually reclaim the pointer immediately if
211
        // the current thread is active. Instead, it adds it to the current thread's
212
        // reclamation list, but we don't guarantee that publicly.
213
0
        unsafe { self.raw.add(ptr, reclaim, Thread::current()) }
214
0
    }
Unexecuted instantiation: <seize::collector::Collector>::retire::<papaya::raw::Entry<bytes::bytes::Bytes, ()>>
Unexecuted instantiation: <seize::collector::Collector>::retire::<_>
215
216
    /// Reclaim any values that have been retired.
217
    ///
218
    /// This method reclaims any objects that have been retired across *all*
219
    /// threads. After calling this method, any values that were previous
220
    /// retired, or retired recursively on the current thread during this
221
    /// call, will have been reclaimed.
222
    ///
223
    /// # Safety
224
    ///
225
    /// This function is **extremely unsafe** to call. It is only sound when no
226
    /// threads are currently active, whether accessing values that have
227
    /// been retired or accessing the collector through any type of guard.
228
    /// This is akin to having a unique reference to the collector. However,
229
    /// this method takes a shared reference, as reclaimers to
230
    /// be run by this thread are allowed to access the collector recursively.
231
    ///
232
    /// # Notes
233
    ///
234
    /// Note that if reclaimers initialize guards across threads, or initialize
235
    /// owned guards, objects retired through those guards may not be
236
    /// reclaimed.
237
20.6k
    pub unsafe fn reclaim_all(&self) {
238
20.6k
        unsafe { self.raw.reclaim_all() };
239
20.6k
    }
240
241
    // Create a reference to `Collector` from an underlying `raw::Collector`.
242
0
    pub(crate) fn from_raw(raw: &raw::Collector) -> &Collector {
243
0
        unsafe { &*(raw as *const raw::Collector as *const Collector) }
244
0
    }
245
}
246
247
impl Eq for Collector {}
248
249
impl PartialEq for Collector {
250
    /// Checks if both references point to the same collector.
251
    #[inline]
252
0
    fn eq(&self, other: &Self) -> bool {
253
0
        self.raw.id == other.raw.id
254
0
    }
255
}
256
257
impl fmt::Debug for Collector {
258
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259
0
        f.debug_struct("Collector")
260
0
            .field("batch_size", &self.raw.batch_size)
261
0
            .finish()
262
0
    }
263
}