crossbeam_epoch/internal.rs
1//! The global data and participant for garbage collection.
2//!
3//! # Registration
4//!
5//! In order to track all participants in one place, we need some form of participant
6//! registration. When a participant is created, it is registered to a global lock-free
7//! singly-linked list of registries; and when a participant is leaving, it is unregistered from the
8//! list.
9//!
10//! # Pinning
11//!
12//! Every participant contains an integer that tells whether the participant is pinned and if so,
13//! what was the global epoch at the time it was pinned. Participants also hold a pin counter that
14//! aids in periodic global epoch advancement.
15//!
16//! When a participant is pinned, a `Guard` is returned as a witness that the participant is pinned.
17//! Guards are necessary for performing atomic operations, and for freeing/dropping locations.
18//!
19//! # Thread-local bag
20//!
21//! Objects that get unlinked from concurrent data structures must be stashed away until the global
22//! epoch sufficiently advances so that they become safe for destruction. Pointers to such objects
23//! are pushed into a thread-local bag, and when it becomes full, the bag is marked with the current
24//! global epoch and pushed into the global queue of bags. We store objects in thread-local storages
25//! for amortizing the synchronization cost of pushing the garbages to a global queue.
26//!
27//! # Global queue
28//!
29//! Whenever a bag is pushed into a queue, the objects in some bags in the queue are collected and
30//! destroyed along the way. This design reduces contention on data structures. The global queue
31//! cannot be explicitly accessed: the only way to interact with it is by calling functions
32//! `defer()` that adds an object to the thread-local bag, or `collect()` that manually triggers
33//! garbage collection.
34//!
35//! Ideally each instance of concurrent data structure may have its own queue that gets fully
36//! destroyed as soon as the data structure gets dropped.
37
38use crate::primitive::cell::UnsafeCell;
39use crate::primitive::sync::atomic::{self, Ordering};
40use core::cell::Cell;
41use core::mem::{self, ManuallyDrop};
42use core::num::Wrapping;
43use core::{fmt, ptr};
44
45use crossbeam_utils::CachePadded;
46
47use crate::atomic::{Owned, Shared};
48use crate::collector::{Collector, LocalHandle};
49use crate::deferred::Deferred;
50use crate::epoch::{AtomicEpoch, Epoch};
51use crate::guard::{unprotected, Guard};
52use crate::sync::list::{Entry, IsElement, IterError, List};
53use crate::sync::queue::Queue;
54
55/// Maximum number of objects a bag can contain.
56#[cfg(not(any(crossbeam_sanitize, miri)))]
57const MAX_OBJECTS: usize = 64;
58// Makes it more likely to trigger any potential data races.
59#[cfg(any(crossbeam_sanitize, miri))]
60const MAX_OBJECTS: usize = 4;
61
62/// A bag of deferred functions.
63pub(crate) struct Bag {
64 /// Stashed objects.
65 deferreds: [Deferred; MAX_OBJECTS],
66 len: usize,
67}
68
69/// `Bag::try_push()` requires that it is safe for another thread to execute the given functions.
70unsafe impl Send for Bag {}
71
72impl Bag {
73 /// Returns a new, empty bag.
74 pub(crate) fn new() -> Self {
75 Self::default()
76 }
77
78 /// Returns `true` if the bag is empty.
79 pub(crate) fn is_empty(&self) -> bool {
80 self.len == 0
81 }
82
83 /// Attempts to insert a deferred function into the bag.
84 ///
85 /// Returns `Ok(())` if successful, and `Err(deferred)` for the given `deferred` if the bag is
86 /// full.
87 ///
88 /// # Safety
89 ///
90 /// It should be safe for another thread to execute the given function.
91 pub(crate) unsafe fn try_push(&mut self, deferred: Deferred) -> Result<(), Deferred> {
92 if self.len < MAX_OBJECTS {
93 self.deferreds[self.len] = deferred;
94 self.len += 1;
95 Ok(())
96 } else {
97 Err(deferred)
98 }
99 }
100
101 /// Seals the bag with the given epoch.
102 fn seal(self, epoch: Epoch) -> SealedBag {
103 SealedBag { epoch, _bag: self }
104 }
105}
106
107impl Default for Bag {
108 fn default() -> Self {
109 Bag {
110 len: 0,
111 deferreds: [Deferred::NO_OP; MAX_OBJECTS],
112 }
113 }
114}
115
116impl Drop for Bag {
117 fn drop(&mut self) {
118 // Call all deferred functions.
119 for deferred in &mut self.deferreds[..self.len] {
120 let no_op = Deferred::NO_OP;
121 let owned_deferred = mem::replace(deferred, no_op);
122 owned_deferred.call();
123 }
124 }
125}
126
127// can't #[derive(Debug)] because Debug is not implemented for arrays 64 items long
128impl fmt::Debug for Bag {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.debug_struct("Bag")
131 .field("deferreds", &&self.deferreds[..self.len])
132 .finish()
133 }
134}
135
136/// A pair of an epoch and a bag.
137#[derive(Default, Debug)]
138struct SealedBag {
139 epoch: Epoch,
140 _bag: Bag,
141}
142
143/// It is safe to share `SealedBag` because `is_expired` only inspects the epoch.
144unsafe impl Sync for SealedBag {}
145
146impl SealedBag {
147 /// Checks if it is safe to drop the bag w.r.t. the given global epoch.
148 fn is_expired(&self, global_epoch: Epoch) -> bool {
149 // A pinned participant can witness at most one epoch advancement. Therefore, any bag that
150 // is within one epoch of the current one cannot be destroyed yet.
151 global_epoch.wrapping_sub(self.epoch) >= 2
152 }
153}
154
155/// The global data for a garbage collector.
156pub(crate) struct Global {
157 /// The intrusive linked list of `Local`s.
158 locals: List<Local>,
159
160 /// The global queue of bags of deferred functions.
161 queue: Queue<SealedBag>,
162
163 /// The global epoch.
164 pub(crate) epoch: CachePadded<AtomicEpoch>,
165}
166
167impl Global {
168 /// Number of bags to destroy.
169 const COLLECT_STEPS: usize = 8;
170
171 /// Creates a new global data for garbage collection.
172 #[inline]
173 pub(crate) fn new() -> Self {
174 Self {
175 locals: List::new(),
176 queue: Queue::new(),
177 epoch: CachePadded::new(AtomicEpoch::new(Epoch::starting())),
178 }
179 }
180
181 /// Pushes the bag into the global queue and replaces the bag with a new empty bag.
182 pub(crate) fn push_bag(&self, bag: &mut Bag, guard: &Guard) {
183 let bag = mem::replace(bag, Bag::new());
184
185 atomic::fence(Ordering::SeqCst);
186
187 let epoch = self.epoch.load(Ordering::Relaxed);
188 self.queue.push(bag.seal(epoch), guard);
189 }
190
191 /// Collects several bags from the global queue and executes deferred functions in them.
192 ///
193 /// Note: This may itself produce garbage and in turn allocate new bags.
194 ///
195 /// `pin()` rarely calls `collect()`, so we want the compiler to place that call on a cold
196 /// path. In other words, we want the compiler to optimize branching for the case when
197 /// `collect()` is not called.
198 #[cold]
199 pub(crate) fn collect(&self, guard: &Guard) {
200 let global_epoch = self.try_advance(guard);
201
202 let steps = if cfg!(crossbeam_sanitize) {
203 usize::MAX
204 } else {
205 Self::COLLECT_STEPS
206 };
207
208 for _ in 0..steps {
209 match self.queue.try_pop_if(
210 &|sealed_bag: &SealedBag| sealed_bag.is_expired(global_epoch),
211 guard,
212 ) {
213 None => break,
214 Some(sealed_bag) => drop(sealed_bag),
215 }
216 }
217 }
218
219 /// Attempts to advance the global epoch.
220 ///
221 /// The global epoch can advance only if all currently pinned participants have been pinned in
222 /// the current epoch.
223 ///
224 /// Returns the current global epoch.
225 ///
226 /// `try_advance()` is annotated `#[cold]` because it is rarely called.
227 #[cold]
228 pub(crate) fn try_advance(&self, guard: &Guard) -> Epoch {
229 // For ThreadSanitizer that does not understand fences, we simulate the effect of `load; fence`.
230 let global_epoch = self.epoch.load(if cfg!(crossbeam_sanitize_thread) {
231 Ordering::Acquire
232 } else {
233 Ordering::Relaxed
234 });
235 atomic::fence(Ordering::SeqCst);
236
237 // For ThreadSanitizer that does not understand fences, we simulate the equivalent effect.
238 // It is unfortunate that allocation is required, but without it, synchronization might
239 // occur in cases where it should not, potentially causing false positives.
240 #[cfg(crossbeam_sanitize_thread)]
241 let mut locals = alloc::vec![];
242 // TODO(stjepang): `Local`s are stored in a linked list because linked lists are fairly
243 // easy to implement in a lock-free manner. However, traversal can be slow due to cache
244 // misses and data dependencies. We should experiment with other data structures as well.
245 for local in self.locals.iter(guard) {
246 match local {
247 Err(IterError::Stalled) => {
248 // A concurrent thread stalled this iteration. That thread might also try to
249 // advance the epoch, in which case we leave the job to it. Otherwise, the
250 // epoch will not be advanced.
251 return global_epoch;
252 }
253 Ok(local) => {
254 let local_epoch = local.epoch.load(Ordering::Relaxed);
255
256 // If the participant was pinned in a different epoch, we cannot advance the
257 // global epoch just yet.
258 if local_epoch.is_pinned() && local_epoch.unpinned() != global_epoch {
259 return global_epoch;
260 }
261
262 #[cfg(crossbeam_sanitize_thread)]
263 locals.push(local);
264 }
265 }
266 }
267 #[cfg(crossbeam_sanitize_thread)]
268 for local in locals {
269 local.epoch.load(Ordering::Acquire);
270 }
271 #[cfg(not(crossbeam_sanitize_thread))]
272 atomic::fence(Ordering::Acquire);
273
274 // All pinned participants were pinned in the current global epoch.
275 // Now let's advance the global epoch...
276 //
277 // Note that if another thread already advanced it before us, this store will simply
278 // overwrite the global epoch with the same value. This is true because `try_advance` was
279 // called from a thread that was pinned in `global_epoch`, and the global epoch cannot be
280 // advanced two steps ahead of it.
281 let new_epoch = global_epoch.successor();
282 self.epoch.store(new_epoch, Ordering::Release);
283 new_epoch
284 }
285}
286
287/// Participant for garbage collection.
288#[repr(C)] // Note: `entry` must be the first field
289pub(crate) struct Local {
290 /// A node in the intrusive linked list of `Local`s.
291 entry: Entry,
292
293 /// A reference to the global data.
294 ///
295 /// When all guards and handles get dropped, this reference is destroyed.
296 collector: UnsafeCell<ManuallyDrop<Collector>>,
297
298 /// The local bag of deferred functions.
299 pub(crate) bag: UnsafeCell<Bag>,
300
301 /// The number of guards keeping this participant pinned.
302 guard_count: Cell<usize>,
303
304 /// The number of active handles.
305 handle_count: Cell<usize>,
306
307 /// Total number of pinnings performed.
308 ///
309 /// This is just an auxiliary counter that sometimes kicks off collection.
310 pin_count: Cell<Wrapping<usize>>,
311
312 /// The local epoch.
313 epoch: CachePadded<AtomicEpoch>,
314}
315
316// Make sure `Local` is less than or equal to 2048 bytes.
317// https://github.com/crossbeam-rs/crossbeam/issues/551
318#[cfg(not(any(crossbeam_sanitize, miri)))] // `crossbeam_sanitize` and `miri` reduce the size of `Local`
319#[test]
320fn local_size() {
321 // TODO: https://github.com/crossbeam-rs/crossbeam/issues/869
322 // assert!(
323 // core::mem::size_of::<Local>() <= 2048,
324 // "An allocation of `Local` should be <= 2048 bytes."
325 // );
326}
327
328impl Local {
329 /// Number of pinnings after which a participant will execute some deferred functions from the
330 /// global queue.
331 const PINNINGS_BETWEEN_COLLECT: usize = 128;
332
333 /// Registers a new `Local` in the provided `Global`.
334 pub(crate) fn register(collector: &Collector) -> LocalHandle {
335 unsafe {
336 // Since we dereference no pointers in this block, it is safe to use `unprotected`.
337
338 let local = Owned::new(Local {
339 entry: Entry::default(),
340 collector: UnsafeCell::new(ManuallyDrop::new(collector.clone())),
341 bag: UnsafeCell::new(Bag::new()),
342 guard_count: Cell::new(0),
343 handle_count: Cell::new(1),
344 pin_count: Cell::new(Wrapping(0)),
345 epoch: CachePadded::new(AtomicEpoch::new(Epoch::starting())),
346 })
347 .into_shared(unprotected());
348 collector.global.locals.insert(local, unprotected());
349 LocalHandle {
350 local: local.as_raw(),
351 }
352 }
353 }
354
355 /// Returns a reference to the `Global` in which this `Local` resides.
356 #[inline]
357 pub(crate) fn global(&self) -> &Global {
358 &self.collector().global
359 }
360
361 /// Returns a reference to the `Collector` in which this `Local` resides.
362 #[inline]
363 pub(crate) fn collector(&self) -> &Collector {
364 self.collector.with(|c| unsafe { &**c })
365 }
366
367 /// Returns `true` if the current participant is pinned.
368 #[inline]
369 pub(crate) fn is_pinned(&self) -> bool {
370 self.guard_count.get() > 0
371 }
372
373 /// Adds `deferred` to the thread-local bag.
374 ///
375 /// # Safety
376 ///
377 /// It should be safe for another thread to execute the given function.
378 pub(crate) unsafe fn defer(&self, mut deferred: Deferred, guard: &Guard) {
379 let bag = self.bag.with_mut(|b| &mut *b);
380
381 while let Err(d) = bag.try_push(deferred) {
382 self.global().push_bag(bag, guard);
383 deferred = d;
384 }
385 }
386
387 pub(crate) fn flush(&self, guard: &Guard) {
388 let bag = self.bag.with_mut(|b| unsafe { &mut *b });
389
390 if !bag.is_empty() {
391 self.global().push_bag(bag, guard);
392 }
393
394 self.global().collect(guard);
395 }
396
397 /// Pins the `Local`.
398 #[inline]
399 pub(crate) fn pin(&self) -> Guard {
400 let guard = Guard { local: self };
401
402 let guard_count = self.guard_count.get();
403 self.guard_count.set(guard_count.checked_add(1).unwrap());
404
405 if guard_count == 0 {
406 let global_epoch = self.global().epoch.load(Ordering::Relaxed);
407 let new_epoch = global_epoch.pinned();
408
409 // Now we must store `new_epoch` into `self.epoch` and execute a `SeqCst` fence.
410 // The fence makes sure that any future loads from `Atomic`s will not happen before
411 // this store.
412 if cfg!(all(
413 any(target_arch = "x86", target_arch = "x86_64"),
414 not(miri)
415 )) {
416 // HACK(stjepang): On x86 architectures there are two different ways of executing
417 // a `SeqCst` fence.
418 //
419 // 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction.
420 // 2. `_.compare_exchange(_, _, SeqCst, SeqCst)`, which compiles into a `lock cmpxchg`
421 // instruction.
422 //
423 // Both instructions have the effect of a full barrier, but benchmarks have shown
424 // that the second one makes pinning faster in this particular case. It is not
425 // clear that this is permitted by the C++ memory model (SC fences work very
426 // differently from SC accesses), but experimental evidence suggests that this
427 // works fine. Using inline assembly would be a viable (and correct) alternative,
428 // but alas, that is not possible on stable Rust.
429 let current = Epoch::starting();
430 let res = self.epoch.compare_exchange(
431 current,
432 new_epoch,
433 Ordering::SeqCst,
434 Ordering::SeqCst,
435 );
436 debug_assert!(res.is_ok(), "participant was expected to be unpinned");
437 // We add a compiler fence to make it less likely for LLVM to do something wrong
438 // here. Formally, this is not enough to get rid of data races; practically,
439 // it should go a long way.
440 atomic::compiler_fence(Ordering::SeqCst);
441 } else {
442 self.epoch.store(new_epoch, Ordering::Relaxed);
443 atomic::fence(Ordering::SeqCst);
444 }
445
446 // Increment the pin counter.
447 let count = self.pin_count.get();
448 self.pin_count.set(count + Wrapping(1));
449
450 // After every `PINNINGS_BETWEEN_COLLECT` try advancing the epoch and collecting
451 // some garbage.
452 if count.0 % Self::PINNINGS_BETWEEN_COLLECT == 0 {
453 self.global().collect(&guard);
454 }
455 }
456
457 guard
458 }
459
460 /// Unpins the `Local`.
461 #[inline]
462 pub(crate) fn unpin(&self) {
463 let guard_count = self.guard_count.get();
464 self.guard_count.set(guard_count - 1);
465
466 if guard_count == 1 {
467 self.epoch.store(Epoch::starting(), Ordering::Release);
468
469 if self.handle_count.get() == 0 {
470 self.finalize();
471 }
472 }
473 }
474
475 /// Unpins and then pins the `Local`.
476 #[inline]
477 pub(crate) fn repin(&self) {
478 let guard_count = self.guard_count.get();
479
480 // Update the local epoch only if there's only one guard.
481 if guard_count == 1 {
482 let epoch = self.epoch.load(Ordering::Relaxed);
483 let global_epoch = self.global().epoch.load(Ordering::Relaxed).pinned();
484
485 // Update the local epoch only if the global epoch is greater than the local epoch.
486 if epoch != global_epoch {
487 // We store the new epoch with `Release` because we need to ensure any memory
488 // accesses from the previous epoch do not leak into the new one.
489 self.epoch.store(global_epoch, Ordering::Release);
490
491 // However, we don't need a following `SeqCst` fence, because it is safe for memory
492 // accesses from the new epoch to be executed before updating the local epoch. At
493 // worse, other threads will see the new epoch late and delay GC slightly.
494 }
495 }
496 }
497
498 /// Increments the handle count.
499 #[inline]
500 pub(crate) fn acquire_handle(&self) {
501 let handle_count = self.handle_count.get();
502 debug_assert!(handle_count >= 1);
503 self.handle_count.set(handle_count + 1);
504 }
505
506 /// Decrements the handle count.
507 #[inline]
508 pub(crate) fn release_handle(&self) {
509 let guard_count = self.guard_count.get();
510 let handle_count = self.handle_count.get();
511 debug_assert!(handle_count >= 1);
512 self.handle_count.set(handle_count - 1);
513
514 if guard_count == 0 && handle_count == 1 {
515 self.finalize();
516 }
517 }
518
519 /// Removes the `Local` from the global linked list.
520 #[cold]
521 fn finalize(&self) {
522 debug_assert_eq!(self.guard_count.get(), 0);
523 debug_assert_eq!(self.handle_count.get(), 0);
524
525 // Temporarily increment handle count. This is required so that the following call to `pin`
526 // doesn't call `finalize` again.
527 self.handle_count.set(1);
528 unsafe {
529 // Pin and move the local bag into the global queue. It's important that `push_bag`
530 // doesn't defer destruction on any new garbage.
531 let guard = &self.pin();
532 self.global()
533 .push_bag(self.bag.with_mut(|b| &mut *b), guard);
534 }
535 // Revert the handle count back to zero.
536 self.handle_count.set(0);
537
538 unsafe {
539 // Take the reference to the `Global` out of this `Local`. Since we're not protected
540 // by a guard at this time, it's crucial that the reference is read before marking the
541 // `Local` as deleted.
542 let collector: Collector = ptr::read(self.collector.with(|c| &*(*c)));
543
544 // Mark this node in the linked list as deleted.
545 self.entry.delete(unprotected());
546
547 // Finally, drop the reference to the global. Note that this might be the last reference
548 // to the `Global`. If so, the global data will be destroyed and all deferred functions
549 // in its queue will be executed.
550 drop(collector);
551 }
552 }
553}
554
555impl IsElement<Self> for Local {
556 fn entry_of(local: &Self) -> &Entry {
557 // SAFETY: `Local` is `repr(C)` and `entry` is the first field of it.
558 unsafe {
559 let entry_ptr = (local as *const Self).cast::<Entry>();
560 &*entry_ptr
561 }
562 }
563
564 unsafe fn element_of(entry: &Entry) -> &Self {
565 // SAFETY: `Local` is `repr(C)` and `entry` is the first field of it.
566 let local_ptr = (entry as *const Entry).cast::<Self>();
567 &*local_ptr
568 }
569
570 unsafe fn finalize(entry: &Entry, guard: &Guard) {
571 guard.defer_destroy(Shared::from(Self::element_of(entry) as *const _));
572 }
573}
574
575#[cfg(all(test, not(crossbeam_loom)))]
576mod tests {
577 use std::sync::atomic::AtomicUsize;
578
579 use super::*;
580
581 #[test]
582 fn check_defer() {
583 static FLAG: AtomicUsize = AtomicUsize::new(0);
584 fn set() {
585 FLAG.store(42, Ordering::Relaxed);
586 }
587
588 let d = Deferred::new(set);
589 assert_eq!(FLAG.load(Ordering::Relaxed), 0);
590 d.call();
591 assert_eq!(FLAG.load(Ordering::Relaxed), 42);
592 }
593
594 #[test]
595 fn check_bag() {
596 static FLAG: AtomicUsize = AtomicUsize::new(0);
597 fn incr() {
598 FLAG.fetch_add(1, Ordering::Relaxed);
599 }
600
601 let mut bag = Bag::new();
602 assert!(bag.is_empty());
603
604 for _ in 0..MAX_OBJECTS {
605 assert!(unsafe { bag.try_push(Deferred::new(incr)).is_ok() });
606 assert!(!bag.is_empty());
607 assert_eq!(FLAG.load(Ordering::Relaxed), 0);
608 }
609
610 let result = unsafe { bag.try_push(Deferred::new(incr)) };
611 assert!(result.is_err());
612 assert!(!bag.is_empty());
613 assert_eq!(FLAG.load(Ordering::Relaxed), 0);
614
615 drop(bag);
616 assert_eq!(FLAG.load(Ordering::Relaxed), MAX_OBJECTS);
617 }
618}