seize/
collector.rs

1use crate::raw::{self, membarrier, Thread};
2use crate::{LocalGuard, OwnedGuard};
3
4use std::fmt;
5use 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)]
18pub struct Collector {
19    /// The underlying raw collector instance.
20    pub(crate) raw: raw::Collector,
21}
22
23impl Default for Collector {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl Collector {
30    /// The default batch size for a new collector.
31    const DEFAULT_BATCH_SIZE: usize = 32;
32
33    /// Creates a new collector.
34    pub fn new() -> Self {
35        // Initialize the `membarrier` module, detecting the presence of
36        // operating-system strong barrier APIs.
37        membarrier::detect();
38
39        // available_parallelism is quite slow (microseconds).
40        static CPUS: OnceLock<usize> = OnceLock::new();
41        let cpus = *CPUS.get_or_init(|| {
42            std::thread::available_parallelism()
43                .map(Into::into)
44                .unwrap_or(1)
45        });
46
47        // Ensure every batch accumulates at least as many entries
48        // as there are threads on the system.
49        let batch_size = cpus.max(Self::DEFAULT_BATCH_SIZE);
50
51        Self {
52            raw: raw::Collector::new(cpus, batch_size),
53        }
54    }
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    pub fn batch_size(mut self, batch_size: usize) -> Self {
72        self.raw.batch_size = batch_size;
73        self
74    }
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    pub fn enter(&self) -> LocalGuard<'_> {
127        LocalGuard::enter(self)
128    }
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    pub fn enter_owned(&self) -> OwnedGuard<'_> {
138        OwnedGuard::enter(self)
139    }
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    pub unsafe fn retire<T>(&self, ptr: *mut T, reclaim: unsafe fn(*mut T, &Collector)) {
208        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        unsafe { self.raw.add(ptr, reclaim, Thread::current()) }
214    }
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    pub unsafe fn reclaim_all(&self) {
238        unsafe { self.raw.reclaim_all() };
239    }
240
241    // Create a reference to `Collector` from an underlying `raw::Collector`.
242    pub(crate) fn from_raw(raw: &raw::Collector) -> &Collector {
243        unsafe { &*(raw as *const raw::Collector as *const Collector) }
244    }
245}
246
247impl Eq for Collector {}
248
249impl PartialEq for Collector {
250    /// Checks if both references point to the same collector.
251    #[inline]
252    fn eq(&self, other: &Self) -> bool {
253        self.raw.id == other.raw.id
254    }
255}
256
257impl fmt::Debug for Collector {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        f.debug_struct("Collector")
260            .field("batch_size", &self.raw.batch_size)
261            .finish()
262    }
263}