seize/collector.rs
1use crate::tls::Thread;
2use crate::{membarrier, raw, LocalGuard, OwnedGuard};
3
4use std::cell::UnsafeCell;
5use std::fmt;
6use std::num::NonZeroU64;
7use std::sync::atomic::{AtomicUsize, Ordering};
8
9/// Fast, efficient, and robust memory reclamation.
10///
11/// A `Collector` manages the allocation and retirement of concurrent objects.
12/// Objects can be safely loaded through *guards*, which can be created using
13/// the [`enter`](Collector::enter) or [`enter_owned`](Collector::enter_owned)
14/// methods.
15pub struct Collector {
16 id: usize,
17 pub(crate) raw: raw::Collector,
18}
19
20unsafe impl Send for Collector {}
21unsafe impl Sync for Collector {}
22
23impl Collector {
24 const DEFAULT_BATCH_SIZE: usize = 64;
25 const DEFAULT_EPOCH_TICK: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(110) };
26
27 /// Creates a new collector.
28 pub fn new() -> Self {
29 static ID: AtomicUsize = AtomicUsize::new(0);
30
31 membarrier::detect();
32 let cpus = std::thread::available_parallelism()
33 .map(Into::into)
34 .unwrap_or(1);
35
36 let batch_size = cpus.min(Self::DEFAULT_BATCH_SIZE);
37
38 Self {
39 raw: raw::Collector::new(cpus, Self::DEFAULT_EPOCH_TICK, batch_size),
40 id: ID.fetch_add(1, Ordering::Relaxed),
41 }
42 }
43
44 /// Sets the frequency of epoch advancement.
45 ///
46 /// Seize uses epochs to protect against stalled threads.
47 /// The more frequently the epoch is advanced, the faster
48 /// stalled threads can be detected. However, it also means
49 /// that threads will have to do work to catch up to the
50 /// current epoch more often.
51 ///
52 /// The default epoch frequency is `110`, meaning that
53 /// the epoch will advance after every 110 values are
54 /// linked to the collector. Benchmarking has shown that
55 /// this is a good tradeoff between throughput and memory
56 /// efficiency.
57 ///
58 /// If `None` is passed epoch tracking, and protection
59 /// against stalled threads, will be disabled completely.
60 pub fn epoch_frequency(mut self, n: Option<NonZeroU64>) -> Self {
61 self.raw.epoch_frequency = n;
62 self
63 }
64
65 /// Sets the number of values that must be in a batch
66 /// before reclamation is attempted.
67 ///
68 /// Retired values are added to thread-local *batches*
69 /// before starting the reclamation process. After
70 /// `batch_size` is hit, values are moved to separate
71 /// *retirement lists*, where reference counting kicks
72 /// in and batches are eventually reclaimed.
73 ///
74 /// A larger batch size means that deallocation is done
75 /// less frequently, but reclamation also becomes more
76 /// expensive due to longer retirement lists needing
77 /// to be traversed and freed.
78 ///
79 /// Note that batch sizes should generally be larger
80 /// than the number of threads accessing objects.
81 ///
82 /// The default batch size is `32`.
83 pub fn batch_size(mut self, batch_size: usize) -> Self {
84 self.raw.batch_size = batch_size;
85 self
86 }
87
88 /// Marks the current thread as active, returning a guard
89 /// that allows protecting loads of concurrent objects. The thread
90 /// will be marked as inactive when the guard is dropped.
91 ///
92 /// See [the guide](crate::guide#starting-operations) for an
93 /// introduction to using guards, or the documentation of [`LocalGuard`]
94 /// for more details.
95 ///
96 /// # Performance
97 ///
98 /// Creating and destroying a guard is about the same as locking and
99 /// unlocking an uncontended `Mutex`, performance-wise. Because of this,
100 /// guards should be re-used across multiple operations if possible.
101 /// However, note that holding a guard prevents the reclamation of any
102 /// concurrent objects retired during it's lifetime, so there is a
103 /// tradeoff between performance and memory usage.
104 ///
105 /// # Examples
106 ///
107 /// ```rust
108 /// # use std::sync::atomic::{AtomicPtr, Ordering};
109 /// # let collector = seize::Collector::new();
110 /// use seize::{reclaim, Linked, Guard};
111 ///
112 /// let ptr = AtomicPtr::new(collector.link_boxed(1_usize));
113 ///
114 /// let guard = collector.enter();
115 /// let value = guard.protect(&ptr, Ordering::Acquire);
116 /// unsafe { assert_eq!(**value, 1) }
117 /// # unsafe { guard.defer_retire(value, reclaim::boxed::<Linked<usize>>) };
118 /// ```
119 ///
120 /// Note that `enter` is reentrant, and it is legal to create
121 /// multiple guards on the same thread. The thread will stay
122 /// marked as active until the last guard is dropped:
123 ///
124 /// ```rust
125 /// # use std::sync::atomic::{AtomicPtr, Ordering};
126 /// # let collector = seize::Collector::new();
127 /// use seize::{reclaim, Linked, Guard};
128 ///
129 /// let ptr = AtomicPtr::new(collector.link_boxed(1_usize));
130 ///
131 /// let guard1 = collector.enter();
132 /// let guard2 = collector.enter();
133 ///
134 /// let value = guard2.protect(&ptr, Ordering::Acquire);
135 /// drop(guard1);
136 /// // the first guard is dropped, but `value`
137 /// // is still safe to access as a guard still
138 /// // exists
139 /// unsafe { assert_eq!(**value, 1) }
140 /// # unsafe { guard2.defer_retire(value, reclaim::boxed::<Linked<usize>>) };
141 /// drop(guard2) // _now_, the thread is marked as inactive
142 /// ```
143 #[inline]
144 pub fn enter(&self) -> LocalGuard<'_> {
145 LocalGuard::enter(self)
146 }
147
148 /// Create an owned guard that protects objects for it's lifetime.
149 ///
150 /// Unlike local guards created with [`enter`](Collector::enter),
151 /// owned guards are independent of the current thread, allowing
152 /// them to implement `Send`. See the documentation of [`OwnedGuard`]
153 /// for more details.
154 #[inline]
155 pub fn enter_owned(&self) -> OwnedGuard<'_> {
156 OwnedGuard::enter(self)
157 }
158
159 /// Create a [`Link`] that can be used to link an object to the collector.
160 ///
161 /// This method is useful when working with a DST where the [`Linked`]
162 /// wrapper cannot be used. See [`AsLink`] for details, or use the
163 /// [`link_value`](Collector::link_value)
164 /// and [`link_boxed`](Collector::link_boxed) helpers.
165 #[inline]
166 pub fn link(&self) -> Link {
167 // Safety: Accessing the reservation of the current thread is always valid.
168 let reservation = unsafe { self.raw.reservation(Thread::current()) };
169
170 Link {
171 node: UnsafeCell::new(self.raw.node(reservation)),
172 }
173 }
174
175 /// Creates a new `Linked` object with the given value.
176 ///
177 /// This is equivalent to:
178 ///
179 /// ```ignore
180 /// Linked {
181 /// value,
182 /// link: collector.link()
183 /// }
184 /// ```
185 #[inline]
186 pub fn link_value<T>(&self, value: T) -> Linked<T> {
187 Linked {
188 link: self.link(),
189 value,
190 }
191 }
192
193 /// Links a value to the collector and allocates it with `Box`.
194 ///
195 /// This is equivalent to:
196 ///
197 /// ```ignore
198 /// Box::into_raw(Box::new(Linked {
199 /// value,
200 /// link: collector.link()
201 /// }))
202 /// ```
203 #[inline]
204 pub fn link_boxed<T>(&self, value: T) -> *mut Linked<T> {
205 Box::into_raw(Box::new(Linked {
206 link: self.link(),
207 value,
208 }))
209 }
210
211 /// Retires a value, running `reclaim` when no threads hold a reference to
212 /// it.
213 ///
214 /// Note that this method is disconnected from any guards on the current
215 /// thread, so the pointer may be reclaimed immediately. Use
216 /// [`Guard::defer_retire`](crate::Guard::defer_retire) if the pointer
217 /// may still be accessed by the current thread.
218 ///
219 /// # Safety
220 ///
221 /// The retired object must no longer be accessible to any thread that
222 /// enters after it is removed. It also cannot be accessed by the
223 /// current thread after `retire` is called.
224 ///
225 /// Retiring the same pointer twice can cause **undefined behavior**, even
226 /// if the reclaimer doesn't free memory.
227 ///
228 /// Additionally, the pointer must be valid to access as a [`Link`], per the
229 /// [`AsLink`] trait, and the reclaimer passed to `retire` must
230 /// correctly free values of type `T`.
231 ///
232 /// # Examples
233 ///
234 /// Common reclaimers are provided by the [`reclaim`](crate::reclaim)
235 /// module.
236 ///
237 /// ```
238 /// # use std::sync::atomic::{AtomicPtr, Ordering};
239 /// # let collector = seize::Collector::new();
240 /// use seize::{reclaim, Linked};
241 ///
242 /// let ptr = AtomicPtr::new(collector.link_boxed(1_usize));
243 ///
244 /// let guard = collector.enter();
245 /// // store the new value
246 /// let old = ptr.swap(collector.link_boxed(2_usize), Ordering::Release);
247 /// // reclaim the old value
248 /// // safety: the `swap` above made the old value unreachable for any new threads
249 /// unsafe { collector.retire(old, reclaim::boxed::<Linked<usize>>) };
250 /// # unsafe { collector.retire(ptr.load(Ordering::Relaxed), reclaim::boxed::<Linked<usize>>) };
251 /// ```
252 ///
253 /// Alternative, a custom reclaimer function can be used:
254 ///
255 /// ```
256 /// # use seize::{Link, Collector, Linked};
257 /// # let collector = Collector::new();
258 /// let value = collector.link_boxed(1);
259 ///
260 /// // safety: the value was never shared
261 /// unsafe {
262 /// collector.retire(value, |link: *mut Link| unsafe {
263 /// // safety: the value retired was of type *mut Linked<i32>
264 /// let ptr: *mut Linked<i32> = Link::cast(link);
265 ///
266 /// // safety: the value was allocated with `link_boxed`
267 /// let value = Box::from_raw(ptr);
268 /// println!("dropping {}", value);
269 /// drop(value);
270 /// });
271 /// }
272 /// ```
273 #[inline]
274 pub unsafe fn retire<T: AsLink>(&self, ptr: *mut T, reclaim: unsafe fn(*mut Link)) {
275 debug_assert!(!ptr.is_null(), "attempted to retire null pointer");
276
277 // Note that `add` doesn't ever actually reclaim the pointer immediately if
278 // the current thread is active. Instead, it adds it to the current thread's
279 // reclamation list, but we don't guarantee that publicly.
280 unsafe { self.raw.add(ptr, reclaim, Thread::current()) }
281 }
282
283 /// Reclaim any values that have been retired.
284 ///
285 /// This method reclaims any objects that have been retired across *all*
286 /// threads. After calling this method, any values that were previous
287 /// retired, or retired recursively on the current thread during this
288 /// call, will have been reclaimed.
289 ///
290 /// # Safety
291 ///
292 /// This function is **extremely unsafe** to call. It is only sound when no
293 /// threads are currently active, whether accessing values that have
294 /// been retired or accessing the collector through any type of guard.
295 /// This is akin to having a unique reference to the collector. However,
296 /// this method takes a shared reference, as reclaimers to be run by this
297 /// thread are allowed to access the collector recursively.
298 ///
299 /// # Notes
300 ///
301 /// Note that if reclaimers initialize guards across threads, or initialize
302 /// owned guards, objects retired through those guards may not be
303 /// reclaimed.
304 pub unsafe fn reclaim_all(&self) {
305 unsafe { self.raw.reclaim_all() };
306 }
307
308 #[inline]
309 pub(crate) fn id_eq(this: &Collector, other: &Collector) -> bool {
310 this.id == other.id
311 }
312}
313
314impl Clone for Collector {
315 /// Creates a new, independent collector with the same configuration as this
316 /// one.
317 fn clone(&self) -> Self {
318 Collector::new()
319 .batch_size(self.raw.batch_size)
320 .epoch_frequency(self.raw.epoch_frequency)
321 }
322}
323
324impl Default for Collector {
325 fn default() -> Self {
326 Self::new()
327 }
328}
329
330impl fmt::Debug for Collector {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 let mut f = f.debug_struct("Collector");
333
334 if self.raw.epoch_frequency.is_some() {
335 f.field("epoch", &self.raw.epoch.load(Ordering::Acquire));
336 }
337
338 f.field("batch_size", &self.raw.batch_size)
339 .field("epoch_frequency", &self.raw.epoch_frequency)
340 .finish()
341 }
342}
343
344/// A link to the collector.
345///
346/// Functions passed to [`retire`](Collector::retire) are called with a
347/// type-erased `Link` pointer. To extract the underlying value from a link,
348/// you can call the [`cast`](Link::cast) method.
349#[repr(transparent)]
350pub struct Link {
351 #[allow(dead_code)]
352 pub(crate) node: UnsafeCell<raw::Node>,
353}
354
355impl Link {
356 /// Cast this `link` to it's underlying type.
357 ///
358 /// Note that while this function is safe, using the returned
359 /// pointer is only sound if the link is in fact a type-erased `T`.
360 /// This means that when casting a link in a reclaimer, the value
361 /// that was retired must be of type `T`.
362 #[inline]
363 pub fn cast<T: AsLink>(link: *mut Link) -> *mut T {
364 link.cast()
365 }
366}
367
368/// A type that can be pointer-cast to and from a [`Link`].
369///
370/// Most types can avoid this trait and work instead with the [`Linked`]
371/// wrapper type. However, if you want more control over the layout of your
372/// type (i.e. are working with a DST), you may need to implement this trait
373/// directly.
374///
375/// # Safety
376///
377/// Types implementing this trait must be marked `#[repr(C)]`
378/// and have a [`Link`] as their **first** field.
379///
380/// # Examples
381///
382/// ```rust
383/// use seize::{AsLink, Collector, Link};
384///
385/// #[repr(C)]
386/// struct Bytes {
387/// // safety invariant: Link must be the first field
388/// link: Link,
389/// values: [*mut u8; 0],
390/// }
391///
392/// // Safety: Bytes is repr(C) and has Link as it's first field
393/// unsafe impl AsLink for Bytes {}
394///
395/// // Deallocate an `Bytes`.
396/// unsafe fn dealloc(ptr: *mut Bytes, collector: &Collector) {
397/// collector.retire(ptr, |link| {
398/// // safety `ptr` is of type *mut Bytes
399/// let link: *mut Bytes = Link::cast(link);
400/// // ..
401/// });
402/// }
403/// ```
404pub unsafe trait AsLink {}
405
406/// A value linked to a collector.
407///
408/// Objects that may be retired must embed the [`Link`] type. This is a
409/// convenience wrapper for linked values that can be created with the
410/// [`Collector::link_value`] or [`Collector::link_boxed`] methods.
411#[repr(C)]
412pub struct Linked<T> {
413 pub link: Link, // Safety Invariant: this field must come first
414 pub value: T,
415}
416
417unsafe impl<T> AsLink for Linked<T> {}
418
419impl<T: PartialEq> PartialEq for Linked<T> {
420 #[inline]
421 fn eq(&self, other: &Self) -> bool {
422 self.value == other.value
423 }
424}
425
426impl<T: Eq> Eq for Linked<T> {}
427
428impl<T: fmt::Debug> fmt::Debug for Linked<T> {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 write!(f, "{:?}", self.value)
431 }
432}
433
434impl<T: fmt::Display> fmt::Display for Linked<T> {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(f, "{}", self.value)
437 }
438}
439
440impl<T> std::ops::Deref for Linked<T> {
441 type Target = T;
442
443 #[inline]
444 fn deref(&self) -> &Self::Target {
445 &self.value
446 }
447}
448
449impl<T> std::ops::DerefMut for Linked<T> {
450 #[inline]
451 fn deref_mut(&mut self) -> &mut Self::Target {
452 &mut self.value
453 }
454}