Skip to main content

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_value()
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        let global_epoch = self.epoch.load(Ordering::Relaxed);
230        atomic::fence(Ordering::SeqCst);
231
232        // For ThreadSanitizer that does not understand fences, we simulate the equivalent effect.
233        // It is unfortunate that allocation is required, but without it, synchronization might
234        // occur in cases where it should not, potentially causing false positives.
235        #[cfg(crossbeam_sanitize_thread)]
236        let mut locals = alloc::vec![];
237        // TODO(stjepang): `Local`s are stored in a linked list because linked lists are fairly
238        // easy to implement in a lock-free manner. However, traversal can be slow due to cache
239        // misses and data dependencies. We should experiment with other data structures as well.
240        for local in self.locals.iter(guard) {
241            match local {
242                Err(IterError::Stalled) => {
243                    // A concurrent thread stalled this iteration. That thread might also try to
244                    // advance the epoch, in which case we leave the job to it. Otherwise, the
245                    // epoch will not be advanced.
246                    return global_epoch;
247                }
248                Ok(local) => {
249                    let local_epoch = local.epoch.load(Ordering::Relaxed);
250
251                    // If the participant was pinned in a different epoch, we cannot advance the
252                    // global epoch just yet.
253                    if local_epoch.is_pinned() && local_epoch.unpinned() != global_epoch {
254                        return global_epoch;
255                    }
256
257                    #[cfg(crossbeam_sanitize_thread)]
258                    locals.push(local);
259                }
260            }
261        }
262        #[cfg(crossbeam_sanitize_thread)]
263        for local in locals {
264            local.epoch.load(Ordering::Acquire);
265        }
266        #[cfg(not(crossbeam_sanitize_thread))]
267        atomic::fence(Ordering::Acquire);
268
269        // All pinned participants were pinned in the current global epoch.
270        // Now let's advance the global epoch...
271        //
272        // Note that if another thread already advanced it before us, this store will simply
273        // overwrite the global epoch with the same value. This is true because `try_advance` was
274        // called from a thread that was pinned in `global_epoch`, and the global epoch cannot be
275        // advanced two steps ahead of it.
276        let new_epoch = global_epoch.successor();
277        self.epoch.store(new_epoch, Ordering::Release);
278        new_epoch
279    }
280}
281
282/// Participant for garbage collection.
283#[repr(C)] // Note: `entry` must be the first field
284pub(crate) struct Local {
285    /// A node in the intrusive linked list of `Local`s.
286    entry: Entry,
287
288    /// A reference to the global data.
289    ///
290    /// When all guards and handles get dropped, this reference is destroyed.
291    collector: UnsafeCell<ManuallyDrop<Collector>>,
292
293    /// The local bag of deferred functions.
294    pub(crate) bag: UnsafeCell<Bag>,
295
296    /// The number of guards keeping this participant pinned.
297    guard_count: Cell<usize>,
298
299    /// The number of active handles.
300    handle_count: Cell<usize>,
301
302    /// Total number of pinnings performed.
303    ///
304    /// This is just an auxiliary counter that sometimes kicks off collection.
305    pin_count: Cell<Wrapping<usize>>,
306
307    /// The local epoch.
308    epoch: CachePadded<AtomicEpoch>,
309}
310
311// Make sure `Local` is less than or equal to 2048 bytes.
312// https://github.com/crossbeam-rs/crossbeam/issues/551
313#[cfg(not(any(crossbeam_sanitize, miri)))] // `crossbeam_sanitize` and `miri` reduce the size of `Local`
314#[test]
315fn local_size() {
316    // TODO: https://github.com/crossbeam-rs/crossbeam/issues/869
317    // assert!(
318    //     core::mem::size_of::<Local>() <= 2048,
319    //     "An allocation of `Local` should be <= 2048 bytes."
320    // );
321}
322
323impl Local {
324    /// Number of pinnings after which a participant will execute some deferred functions from the
325    /// global queue.
326    const PINNINGS_BETWEEN_COLLECT: usize = 128;
327
328    /// Registers a new `Local` in the provided `Global`.
329    pub(crate) fn register(collector: &Collector) -> LocalHandle {
330        unsafe {
331            // Since we dereference no pointers in this block, it is safe to use `unprotected`.
332
333            let local = Owned::new(Local {
334                entry: Entry::default(),
335                collector: UnsafeCell::new(ManuallyDrop::new(collector.clone())),
336                bag: UnsafeCell::new(Bag::new()),
337                guard_count: Cell::new(0),
338                handle_count: Cell::new(1),
339                pin_count: Cell::new(Wrapping(0)),
340                epoch: CachePadded::new(AtomicEpoch::new(Epoch::starting())),
341            })
342            .into_shared(unprotected());
343            collector.global.locals.insert(local, unprotected());
344            LocalHandle {
345                local: local.as_raw(),
346            }
347        }
348    }
349
350    /// Returns a reference to the `Global` in which this `Local` resides.
351    #[inline]
352    pub(crate) fn global(&self) -> &Global {
353        &self.collector().global
354    }
355
356    /// Returns a reference to the `Collector` in which this `Local` resides.
357    #[inline]
358    pub(crate) fn collector(&self) -> &Collector {
359        self.collector.with(|c| unsafe { &**c })
360    }
361
362    /// Returns `true` if the current participant is pinned.
363    #[inline]
364    pub(crate) fn is_pinned(&self) -> bool {
365        self.guard_count.get() > 0
366    }
367
368    /// Adds `deferred` to the thread-local bag.
369    ///
370    /// # Safety
371    ///
372    /// It should be safe for another thread to execute the given function.
373    pub(crate) unsafe fn defer(&self, mut deferred: Deferred, guard: &Guard) {
374        let bag = self.bag.with_mut(|b| &mut *b);
375
376        while let Err(d) = bag.try_push(deferred) {
377            self.global().push_bag(bag, guard);
378            deferred = d;
379        }
380    }
381
382    pub(crate) fn flush(&self, guard: &Guard) {
383        let bag = self.bag.with_mut(|b| unsafe { &mut *b });
384
385        if !bag.is_empty() {
386            self.global().push_bag(bag, guard);
387        }
388
389        self.global().collect(guard);
390    }
391
392    /// Pins the `Local`.
393    #[inline]
394    pub(crate) fn pin(&self) -> Guard {
395        let guard = Guard { local: self };
396
397        let guard_count = self.guard_count.get();
398        self.guard_count.set(guard_count.checked_add(1).unwrap());
399
400        if guard_count == 0 {
401            let global_epoch = self.global().epoch.load(Ordering::Relaxed);
402            let new_epoch = global_epoch.pinned();
403
404            // Now we must store `new_epoch` into `self.epoch` and execute a `SeqCst` fence.
405            // The fence makes sure that any future loads from `Atomic`s will not happen before
406            // this store.
407            if cfg!(all(
408                any(target_arch = "x86", target_arch = "x86_64"),
409                not(miri)
410            )) {
411                // HACK(stjepang): On x86 architectures there are two different ways of executing
412                // a `SeqCst` fence.
413                //
414                // 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction.
415                // 2. `_.compare_exchange(_, _, SeqCst, SeqCst)`, which compiles into a `lock cmpxchg`
416                //    instruction.
417                //
418                // Both instructions have the effect of a full barrier, but benchmarks have shown
419                // that the second one makes pinning faster in this particular case.  It is not
420                // clear that this is permitted by the C++ memory model (SC fences work very
421                // differently from SC accesses), but experimental evidence suggests that this
422                // works fine.  Using inline assembly would be a viable (and correct) alternative,
423                // but alas, that is not possible on stable Rust.
424                let current = Epoch::starting();
425                let res = self.epoch.compare_exchange(
426                    current,
427                    new_epoch,
428                    Ordering::SeqCst,
429                    Ordering::SeqCst,
430                );
431                debug_assert!(res.is_ok(), "participant was expected to be unpinned");
432                // We add a compiler fence to make it less likely for LLVM to do something wrong
433                // here.  Formally, this is not enough to get rid of data races; practically,
434                // it should go a long way.
435                atomic::compiler_fence(Ordering::SeqCst);
436            } else {
437                self.epoch.store(new_epoch, Ordering::Relaxed);
438                atomic::fence(Ordering::SeqCst);
439            }
440
441            // Increment the pin counter.
442            let count = self.pin_count.get();
443            self.pin_count.set(count + Wrapping(1));
444
445            // After every `PINNINGS_BETWEEN_COLLECT` try advancing the epoch and collecting
446            // some garbage.
447            if count.0 % Self::PINNINGS_BETWEEN_COLLECT == 0 {
448                self.global().collect(&guard);
449            }
450        }
451
452        guard
453    }
454
455    /// Unpins the `Local`.
456    #[inline]
457    pub(crate) fn unpin(&self) {
458        let guard_count = self.guard_count.get();
459        self.guard_count.set(guard_count - 1);
460
461        if guard_count == 1 {
462            self.epoch.store(Epoch::starting(), Ordering::Release);
463
464            if self.handle_count.get() == 0 {
465                self.finalize();
466            }
467        }
468    }
469
470    /// Unpins and then pins the `Local`.
471    #[inline]
472    pub(crate) fn repin(&self) {
473        let guard_count = self.guard_count.get();
474
475        // Update the local epoch only if there's only one guard.
476        if guard_count == 1 {
477            let epoch = self.epoch.load(Ordering::Relaxed);
478            let global_epoch = self.global().epoch.load(Ordering::Relaxed).pinned();
479
480            // Update the local epoch only if the global epoch is greater than the local epoch.
481            if epoch != global_epoch {
482                // We store the new epoch with `Release` because we need to ensure any memory
483                // accesses from the previous epoch do not leak into the new one.
484                self.epoch.store(global_epoch, Ordering::Release);
485
486                // However, we don't need a following `SeqCst` fence, because it is safe for memory
487                // accesses from the new epoch to be executed before updating the local epoch. At
488                // worse, other threads will see the new epoch late and delay GC slightly.
489            }
490        }
491    }
492
493    /// Increments the handle count.
494    #[inline]
495    pub(crate) fn acquire_handle(&self) {
496        let handle_count = self.handle_count.get();
497        debug_assert!(handle_count >= 1);
498        self.handle_count.set(handle_count + 1);
499    }
500
501    /// Decrements the handle count.
502    #[inline]
503    pub(crate) fn release_handle(&self) {
504        let guard_count = self.guard_count.get();
505        let handle_count = self.handle_count.get();
506        debug_assert!(handle_count >= 1);
507        self.handle_count.set(handle_count - 1);
508
509        if guard_count == 0 && handle_count == 1 {
510            self.finalize();
511        }
512    }
513
514    /// Removes the `Local` from the global linked list.
515    #[cold]
516    fn finalize(&self) {
517        debug_assert_eq!(self.guard_count.get(), 0);
518        debug_assert_eq!(self.handle_count.get(), 0);
519
520        // Temporarily increment handle count. This is required so that the following call to `pin`
521        // doesn't call `finalize` again.
522        self.handle_count.set(1);
523        unsafe {
524            // Pin and move the local bag into the global queue. It's important that `push_bag`
525            // doesn't defer destruction on any new garbage.
526            let guard = &self.pin();
527            self.global()
528                .push_bag(self.bag.with_mut(|b| &mut *b), guard);
529        }
530        // Revert the handle count back to zero.
531        self.handle_count.set(0);
532
533        unsafe {
534            // Take the reference to the `Global` out of this `Local`. Since we're not protected
535            // by a guard at this time, it's crucial that the reference is read before marking the
536            // `Local` as deleted.
537            let collector: Collector = ptr::read(self.collector.with(|c| &*(*c)));
538
539            // Mark this node in the linked list as deleted.
540            self.entry.delete(unprotected());
541
542            // Finally, drop the reference to the global. Note that this might be the last reference
543            // to the `Global`. If so, the global data will be destroyed and all deferred functions
544            // in its queue will be executed.
545            drop(collector);
546        }
547    }
548}
549
550impl IsElement<Self> for Local {
551    fn entry_of(local: &Self) -> &Entry {
552        // SAFETY: `Local` is `repr(C)` and `entry` is the first field of it.
553        unsafe {
554            let entry_ptr = (local as *const Self).cast::<Entry>();
555            &*entry_ptr
556        }
557    }
558
559    unsafe fn element_of(entry: &Entry) -> &Self {
560        // SAFETY: `Local` is `repr(C)` and `entry` is the first field of it.
561        let local_ptr = (entry as *const Entry).cast::<Self>();
562        &*local_ptr
563    }
564
565    unsafe fn finalize(entry: &Entry, guard: &Guard) {
566        guard.defer_destroy(Shared::from(Self::element_of(entry) as *const _));
567    }
568}
569
570#[cfg(all(test, not(crossbeam_loom)))]
571mod tests {
572    use std::sync::atomic::AtomicUsize;
573
574    use super::*;
575
576    #[test]
577    fn check_defer() {
578        static FLAG: AtomicUsize = AtomicUsize::new(0);
579        fn set() {
580            FLAG.store(42, Ordering::Relaxed);
581        }
582
583        let d = Deferred::new(set);
584        assert_eq!(FLAG.load(Ordering::Relaxed), 0);
585        d.call();
586        assert_eq!(FLAG.load(Ordering::Relaxed), 42);
587    }
588
589    #[test]
590    fn check_bag() {
591        static FLAG: AtomicUsize = AtomicUsize::new(0);
592        fn incr() {
593            FLAG.fetch_add(1, Ordering::Relaxed);
594        }
595
596        let mut bag = Bag::new();
597        assert!(bag.is_empty());
598
599        for _ in 0..MAX_OBJECTS {
600            assert!(unsafe { bag.try_push(Deferred::new(incr)).is_ok() });
601            assert!(!bag.is_empty());
602            assert_eq!(FLAG.load(Ordering::Relaxed), 0);
603        }
604
605        let result = unsafe { bag.try_push(Deferred::new(incr)) };
606        assert!(result.is_err());
607        assert!(!bag.is_empty());
608        assert_eq!(FLAG.load(Ordering::Relaxed), 0);
609
610        drop(bag);
611        assert_eq!(FLAG.load(Ordering::Relaxed), MAX_OBJECTS);
612    }
613}