Skip to main content

fuchsia_rcu/
state_machine.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::atomic_stack::{AtomicListIterator, AtomicStack};
6use crate::rcu_droppable::RcuDroppable;
7use fuchsia_sync::{Completion, Mutex};
8use std::marker::PhantomData;
9use std::sync::atomic::{AtomicPtr, AtomicU8, AtomicUsize, Ordering};
10use std::thread_local;
11use std::time::Duration;
12
13#[cfg(feature = "rseq_backend")]
14use crate::read_counters::RcuReadCounters;
15
16type RcuCallback = Box<dyn FnOnce() + Send + Sync + 'static>;
17
18struct RcuControlBlock {
19    /// The generation counter.
20    ///
21    /// The generation counter is incremented whenever the state machine leaves the `Idle` state.
22    generation: AtomicUsize,
23
24    /// The read counters.
25    ///
26    /// Readers increment the counter for the generation that they are reading from. For example,
27    /// if the `generation` is even, then readers increment the counter for the `read_counters[0]`.
28    /// If the `generation` is odd, then readers increment the counter for the `read_counters[1]`.
29    #[cfg(not(feature = "rseq_backend"))]
30    read_counters: [AtomicUsize; 2],
31
32    #[cfg(feature = "rseq_backend")]
33    read_counters: RcuReadCounters,
34
35    /// The chain of callbacks that are waiting to be run.
36    ///
37    /// Writers add callbacks to this chain after writing to the object. The callbacks are run when
38    /// all currently in-flight read operations have completed.
39    callback_chain: AtomicStack<RcuCallback>,
40
41    /// The futex used to put the background advancer thread to sleep when there are no callbacks.
42    advancer_thread_state: zx::Futex,
43
44    /// Callbacks that are ready to run after the next grace period.
45    waiting_callbacks: Mutex<AtomicListIterator<RcuCallback>>,
46}
47
48const ADVANCER_THREAD_SLEEPING: i32 = 0;
49const ADVANCER_THREAD_ACTIVE: i32 = 1;
50
51/// The number of times to spin checking for active readers before yielding.
52const ADVANCER_SPIN_LIMIT: u32 = 64;
53/// The number of spin/yield iterations before falling back to sleeping.
54const ADVANCER_YIELD_LIMIT: u32 = 66;
55/// Initial sleep duration when active readers are still present after spinning and yielding.
56const ADVANCER_INITIAL_SLEEP: Duration = Duration::from_micros(50);
57/// Maximum sleep duration for exponential backoff during long stalls.
58const ADVANCER_MAX_SLEEP: Duration = Duration::from_millis(1);
59
60impl RcuControlBlock {
61    /// Create a new control block for the RCU state machine.
62    const fn new() -> Self {
63        #[cfg(feature = "rseq_backend")]
64        let read_counters = RcuReadCounters::new();
65
66        #[cfg(not(feature = "rseq_backend"))]
67        let read_counters = [AtomicUsize::new(0), AtomicUsize::new(0)];
68
69        Self {
70            generation: AtomicUsize::new(0),
71            read_counters,
72            callback_chain: AtomicStack::new(),
73            advancer_thread_state: zx::Futex::new(ADVANCER_THREAD_SLEEPING),
74            waiting_callbacks: Mutex::new(AtomicListIterator::empty()),
75        }
76    }
77}
78
79/// The control block for the RCU state machine.
80static RCU_CONTROL_BLOCK: RcuControlBlock = RcuControlBlock::new();
81
82struct RcuThreadBlock {
83    /// The number of times the thread has nested into a read lock.
84    nesting_level: AtomicUsize,
85
86    /// The index of the read counter that the thread incremented when it entered its outermost read
87    /// lock.
88    counter_index: AtomicU8,
89}
90
91impl RcuThreadBlock {
92    /// Creates a new `RcuThreadBlock`.
93    const fn new() -> Self {
94        Self { nesting_level: AtomicUsize::new(0), counter_index: AtomicU8::new(0) }
95    }
96
97    /// Returns true if the thread is holding a read lock.
98    fn holding_read_lock(&self) -> bool {
99        self.nesting_level.load(Ordering::Relaxed) > 0
100    }
101}
102thread_local! {
103    /// Thread-specific data for the RCU state machine.
104    ///
105    /// This data is used to track the nesting level of read locks and the index of the read counter
106    /// that the thread incremented when it entered its outermost read lock.
107    static RCU_THREAD_BLOCK: RcuThreadBlock = const { RcuThreadBlock::new() };
108}
109
110/// An RAII guard that keeps the current thread registered with RCU and RSEQ.
111///
112/// Unregisters the thread when dropped.
113#[derive(Debug)]
114#[must_use = "the thread is unregistered when the guard is dropped"]
115pub struct RcuThreadRegistration {
116    _marker: PhantomData<*const ()>,
117}
118
119impl RcuThreadRegistration {
120    /// Leaks the registration, keeping the current thread registered indefinitely.
121    #[inline]
122    pub fn leak(self) {
123        std::mem::forget(self);
124    }
125}
126
127impl Drop for RcuThreadRegistration {
128    fn drop(&mut self) {
129        unregister_thread();
130    }
131}
132
133/// Registers the current thread for RCU and RSEQ.
134pub fn register_thread() -> RcuThreadRegistration {
135    #[cfg(feature = "rseq_backend")]
136    fuchsia_rseq::rseq_register_thread_with_cs(crate::read_counters::rcu_critical_section());
137
138    RcuThreadRegistration { _marker: PhantomData }
139}
140
141/// Unregisters the current thread from RCU and RSEQ.
142pub fn unregister_thread() {
143    #[cfg(feature = "rseq_backend")]
144    fuchsia_rseq::rseq_unregister_thread();
145}
146
147/// Exposes the thread-local counters for RCU stall detection.
148pub fn with_thread_block_counters<F>(f: F)
149where
150    F: FnOnce(*const AtomicUsize, *const AtomicU8),
151{
152    RCU_THREAD_BLOCK.with(|thread_block| {
153        f(&thread_block.nesting_level as *const _, &thread_block.counter_index as *const _);
154    });
155}
156
157/// Acquire a read lock.
158///
159/// This function is used to acquire a read lock on the RCU state machine. The RCU state machine
160/// defers calling callbacks until all currently in-flight read operations have completed.
161///
162/// Must be balanced by a call to `rcu_read_unlock` on the same thread.
163#[inline]
164pub(crate) fn rcu_read_lock() {
165    RCU_THREAD_BLOCK.with(|thread_block| {
166        let nesting_level = thread_block.nesting_level.load(Ordering::Relaxed);
167        if nesting_level > 0 {
168            // If this thread already has a read lock, increment the nesting level instead of the
169            // incrementing the read counter. This approach is a performance optimization to reduce
170            // the number of atomic operations that need to be performed.
171            thread_block.nesting_level.store(nesting_level + 1, Ordering::Relaxed);
172        } else {
173            // This is the outermost read lock. Increment the read counter.
174            let control_block = &RCU_CONTROL_BLOCK;
175
176            // There's a race here where we capture `index` and then go on to increment the read
177            // counter.  The choice of `index` here isn't actually important for correctness because
178            // we always wait at least two grace periods before calling the callbacks, so it doesn't
179            // matter which counter we increment.  It does mean that a thread waiting for the read
180            // counter to drop to zero, could actually find that the read counter increases before
181            // it eventually reaches zero, which should be fine.
182            let index = control_block.generation.load(Ordering::Relaxed) & 1;
183
184            #[cfg(feature = "rseq_backend")]
185            {
186                control_block.read_counters.begin(index);
187                std::sync::atomic::compiler_fence(Ordering::SeqCst);
188            }
189
190            #[cfg(not(feature = "rseq_backend"))]
191            {
192                // Synchronization point [A] (see design.md)
193                control_block.read_counters[index].fetch_add(1, Ordering::SeqCst);
194            }
195
196            thread_block.counter_index.store(index as u8, Ordering::Relaxed);
197            thread_block.nesting_level.store(1, Ordering::Relaxed);
198        }
199    });
200}
201
202/// Release a read lock.
203///
204/// This function is used to release a read lock on the RCU state machine. See `rcu_read_lock` for
205/// more details.
206#[inline]
207pub(crate) fn rcu_read_unlock() {
208    RCU_THREAD_BLOCK.with(|thread_block| {
209        let nesting_level = thread_block.nesting_level.load(Ordering::Relaxed);
210        if nesting_level > 1 {
211            // If the nesting level is greater than 1, this is not the outermost read lock.
212            // Decrement the nesting level instead of the read counter.
213            thread_block.nesting_level.store(nesting_level - 1, Ordering::Relaxed);
214        } else {
215            // This is the outermost read lock. Decrement the read counter.
216            let index = thread_block.counter_index.load(Ordering::Relaxed) as usize;
217            let control_block = &RCU_CONTROL_BLOCK;
218
219            #[cfg(feature = "rseq_backend")]
220            {
221                std::sync::atomic::compiler_fence(Ordering::SeqCst);
222                control_block.read_counters.end(index);
223            }
224
225            #[cfg(not(feature = "rseq_backend"))]
226            {
227                // Synchronization point [B] (see design.md)
228                control_block.read_counters[index].fetch_sub(1, Ordering::SeqCst);
229            }
230
231            thread_block.nesting_level.store(0, Ordering::Relaxed);
232            thread_block.counter_index.store(u8::MAX, Ordering::Relaxed);
233        }
234    });
235}
236
237/// Read the value of an RCU pointer.
238///
239/// This function cannot be called unless the current thread is holding a read lock. The returned
240/// pointer is valid until the read lock is released.
241pub(crate) fn rcu_read_pointer<T>(ptr: &AtomicPtr<T>) -> *const T {
242    // Synchronization point [D] (see design.md)
243    ptr.load(Ordering::Acquire)
244}
245
246/// Assign a new value to an RCU pointer.
247///
248/// Concurrent readers may continue to reference the old value of the pointer until the RCU state
249/// machine has made sufficient progress. To clean up the old value of the pointer, use `rcu_call`
250/// or `rcu_drop`, which defer processing until all in-flight read operations have completed.
251pub(crate) fn rcu_assign_pointer<T>(ptr: &AtomicPtr<T>, new_ptr: *mut T) {
252    // Synchronization point [E] (see design.md)
253    ptr.store(new_ptr, Ordering::Release);
254}
255
256/// Replace the value of an RCU pointer.
257///
258/// Concurrent readers may continue to reference the old value of the pointer until the RCU state
259/// machine has made sufficient progress. To clean up the old value of the pointer, use `rcu_call`
260/// or `rcu_drop`, which defer processing until all in-flight read operations have completed.
261pub(crate) fn rcu_replace_pointer<T>(ptr: &AtomicPtr<T>, new_ptr: *mut T) -> *mut T {
262    // Synchronization point [F] (see design.md)
263    ptr.swap(new_ptr, Ordering::AcqRel)
264}
265
266/// Call a callback to run after all in-flight read operations have completed.
267///
268/// To wait until the callback is ready to run, call `rcu_synchronize()`. Note that
269/// `rcu_synchronize()` requires an advancer thread or a thread calling `rcu_run_callbacks()` to
270/// make progress and invoke callbacks. The callback might be called from an arbitrary thread.
271///
272/// NOTE: The order in which callbacks are called is not guaranteed since they can be called
273/// concurrently from multiple threads.
274pub(crate) fn rcu_call(callback: impl FnOnce() + Send + Sync + 'static) {
275    #[cfg(not(feature = "rseq_backend"))]
276    {
277        // We need to synchronize with rcu_read_lock.  We need to ensure that all prior stores are
278        // visible to threads that have called rcu_read_lock.  We must synchronize with both read
279        // counters using a store operation.  We don't need to change the value.
280        std::sync::atomic::fence(Ordering::Release);
281
282        RCU_CONTROL_BLOCK.read_counters[0].fetch_add(0, Ordering::Relaxed);
283        RCU_CONTROL_BLOCK.read_counters[1].fetch_add(0, Ordering::Relaxed);
284    }
285
286    // Synchronization point [G] (see design.md)
287    RCU_CONTROL_BLOCK.callback_chain.push_front(Box::new(callback));
288
289    // Wake the rcu advancer thread if it is sleeping on the futex.
290    let thread_state = &RCU_CONTROL_BLOCK.advancer_thread_state;
291
292    // This write is required to be SeqCst to ensure total ordering with additions to the
293    // callback_chain. See the comment in rcu_advancer_wait_for_work for details.
294    if thread_state.swap(ADVANCER_THREAD_ACTIVE, Ordering::SeqCst) == ADVANCER_THREAD_SLEEPING {
295        thread_state.wake(1);
296    }
297}
298
299/// Schedule the object to be dropped after all in-flight read operations have completed.
300///
301/// To wait until the object is dropped, call `rcu_synchronize()`. Note that `rcu_synchronize()`
302/// requires an advancer thread or a thread calling `rcu_run_callbacks()` to make progress and drop
303/// the object.
304///
305/// To be safely passed to [rcu_drop] either directly, or indirectly by an rcu container, the type
306/// must implement the marker trait [RcuDroppable] to indicate:
307/// - Dropping T must not take locks or otherwise block.
308/// - It is safe to drop T from an arbitrary thread.
309/// - There is no guarantee as to _when_ T will actually be dropped unless `rcu_synchronize()` or
310///   `rcu_run_callbacks()` is called.
311pub fn rcu_drop<T: RcuDroppable + Sync>(value: T) {
312    rcu_call(move || {
313        std::mem::drop(value);
314    });
315}
316
317/// Check if there are any active readers for the given generation.
318fn has_active_readers(generation: usize) -> bool {
319    let index = generation & 1;
320
321    #[cfg(feature = "rseq_backend")]
322    {
323        return RCU_CONTROL_BLOCK.read_counters.has_active(index);
324    }
325
326    #[cfg(not(feature = "rseq_backend"))]
327    {
328        // Synchronization point [C] (see design.md)
329        RCU_CONTROL_BLOCK.read_counters[index].load(Ordering::SeqCst) > 0
330    }
331}
332
333/// Wake the rcu advancer thread if it's sleeping.
334pub fn rcu_advancer_wake() {
335    // Scheduling a no-op callback is a convenient way to both wake the rcu advancer and also have
336    // it consider the wakeup to be non spurious so it doesn't immediately go back to sleep.
337    rcu_call(|| {});
338}
339
340/// Blocks the current thread until all in-flight read operations have completed for the given
341/// generation.
342///
343/// Postcondition: The number of active readers for the given generation is zero.
344fn rcu_advancer_wait_for_readers(generation: usize) {
345    let mut spins = 0u32;
346    let mut sleep_duration = ADVANCER_INITIAL_SLEEP;
347    while has_active_readers(generation) {
348        // In practice, we tend to see a bimodel distribution of read locks that either release
349        // within a few hundred ns, or around a few µs.
350        //
351        // Then, we see long tail of cases where the release can take > 1ms because a thread got
352        // context switched out while holding a read lock.
353        //
354        // We attempt to model this behavior by first spinning, then slowly backing off.
355        if spins < ADVANCER_SPIN_LIMIT {
356            std::hint::spin_loop();
357            spins += 1;
358        } else if spins < ADVANCER_YIELD_LIMIT {
359            std::thread::yield_now();
360            spins += 1;
361        } else {
362            std::thread::sleep(sleep_duration);
363            sleep_duration = std::cmp::min(sleep_duration * 2, ADVANCER_MAX_SLEEP);
364        }
365    }
366}
367
368/// Advance the RCU state machine.
369///
370/// This function blocks until all in-flight read operations have completed for the current
371/// generation and all callbacks have been run.
372fn rcu_grace_period() {
373    let callbacks = {
374        let mut waiting_callbacks = RCU_CONTROL_BLOCK.waiting_callbacks.lock();
375
376        // We are in the *Idle* state.
377
378        // Swap out the callbacks that we can run when this grace period has passed with the
379        // callbacks that can run after the next period.
380        // Synchronization point [H] (see design.md)
381        let callbacks =
382            std::mem::replace(&mut *waiting_callbacks, RCU_CONTROL_BLOCK.callback_chain.take());
383
384        // Issue an IPI to all CPUs to force them to serialize their execution.
385        // This ensures that all prior stores by all writers are visible to
386        // any thread that subsequently enters an RCU read-side critical section.
387        #[cfg(feature = "rseq_backend")]
388        unsafe {
389            zx::sys::zx_membarrier_sync_process_data()
390        };
391
392        let generation = RCU_CONTROL_BLOCK.generation.fetch_add(1, Ordering::Relaxed);
393
394        // Enter the *Waiting* state
395        rcu_advancer_wait_for_readers(generation);
396
397        // Return to the *Idle* state.
398        callbacks
399    };
400
401    for callback in callbacks {
402        callback();
403    }
404}
405
406/// Block until all in-flight read operations have completed for callbacks registered prior to this
407/// call.
408///
409/// Note: This function does not advance the RCU state machine itself; it registers a callback and
410/// blocks until that callback is run. If no thread is calling `rcu_run_callbacks()` (for example,
411/// via a dedicated advancer thread), this function will block indefinitely.
412pub fn rcu_synchronize() {
413    RCU_THREAD_BLOCK.with(|block| {
414        assert!(!block.holding_read_lock());
415    });
416
417    let completion = std::sync::Arc::new(Completion::new());
418    let c = completion.clone();
419    rcu_call(move || {
420        c.signal();
421    });
422
423    // If callbacks have run, then we know all read operations must have completed for the
424    // generation we registered the callback on.
425    completion.wait();
426}
427
428/// Check if there is any work waiting to be processed by the RCU advancer.
429fn has_pending_work() -> bool {
430    !RCU_CONTROL_BLOCK.callback_chain.is_empty()
431        || !RCU_CONTROL_BLOCK.waiting_callbacks.lock().is_empty()
432}
433
434/// Blocks the calling advancer thread until RCU callbacks are scheduled.
435pub fn rcu_advancer_wait_for_work() {
436    let thread_state = &RCU_CONTROL_BLOCK.advancer_thread_state;
437    while !has_pending_work() {
438        // This needs to be SeqCst to make the has_pending_work() call synchronize properly, as
439        // with the write in rcu_call. Without a SeqCst, it's possible that a waker sees our write,
440        // and thus doesn't wake us, and simultaneously, we don't see the pending work, and thus go
441        // to sleep, causing a lost wakeup.
442        //
443        // With the SeqCst total ordering, we're guaranteed that either a thread scheduling
444        // callbacks doesn't observe our write, and thus tries to wake us, or that we observe the
445        // added callback, and thus don't sleep.
446        thread_state.store(ADVANCER_THREAD_SLEEPING, Ordering::SeqCst);
447
448        // Double-check after storing SLEEPING to prevent race with rcu_call, rcu_synchronize, or
449        // rcu_advancer_wake.
450        if has_pending_work() {
451            break;
452        }
453
454        // In the case of a spurious wakeup, we recheck has_pending_work and if there is no work to
455        // be done, attempt to return to sleep.
456        let _ = thread_state.wait(ADVANCER_THREAD_SLEEPING, None, zx::MonotonicInstant::INFINITE);
457    }
458    thread_state.store(ADVANCER_THREAD_ACTIVE, Ordering::Relaxed);
459}
460
461/// Advances the RCU state machine if work is pending and runs ready callbacks.
462///
463/// If callbacks are pending, this runs two grace periods and invokes any ready callbacks.
464///
465/// Returns `true` if callbacks were processed, or `false` if no work was pending.
466pub fn rcu_run_callbacks() -> bool {
467    RCU_THREAD_BLOCK.with(|block| {
468        assert!(!block.holding_read_lock());
469    });
470
471    let thread_state = &RCU_CONTROL_BLOCK.advancer_thread_state;
472    thread_state.store(ADVANCER_THREAD_ACTIVE, Ordering::Relaxed);
473
474    if has_pending_work() {
475        rcu_grace_period();
476        rcu_grace_period();
477        true
478    } else {
479        false
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486    use std::sync::Arc;
487    use std::sync::atomic::{AtomicBool, Ordering};
488
489    static TEST_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
490
491    #[test]
492    fn test_rcu_delay_regression() {
493        let _lock = TEST_MUTEX.lock().unwrap();
494        let flag = Arc::new(AtomicBool::new(false));
495        let moved_flag = flag.clone();
496
497        rcu_call(move || {
498            moved_flag.store(true, Ordering::SeqCst);
499        });
500
501        rcu_grace_period();
502
503        assert!(
504            !flag.load(Ordering::SeqCst),
505            "Callback executed too early! RCU requires 2 grace periods delay."
506        );
507
508        rcu_grace_period();
509        assert!(flag.load(Ordering::SeqCst), "Callback should have executed after 2 grace periods");
510    }
511
512    #[test]
513    fn test_rcu_synchronize() {
514        let _lock = TEST_MUTEX.lock().unwrap();
515        let stop = Arc::new(AtomicBool::new(false));
516        let stop_clone = stop.clone();
517
518        let handle = std::thread::spawn(move || {
519            while !stop_clone.load(Ordering::Relaxed) {
520                rcu_advancer_wait_for_work();
521                rcu_run_callbacks();
522            }
523            rcu_run_callbacks();
524        });
525
526        let completion = Arc::new(Completion::new());
527        let c = completion.clone();
528
529        rcu_call(move || {
530            c.signal();
531        });
532
533        rcu_synchronize();
534
535        // Callbacks within a batch are not guaranteed to execute in a specific order,
536        // so the callback may complete slightly before or after rcu_synchronize() returns.
537        completion.wait();
538
539        stop.store(true, Ordering::Relaxed);
540        rcu_advancer_wake();
541        handle.join().unwrap();
542        RCU_CONTROL_BLOCK.advancer_thread_state.store(ADVANCER_THREAD_SLEEPING, Ordering::SeqCst);
543    }
544
545    #[test]
546    fn test_rcu_run_callbacks() {
547        let _lock = TEST_MUTEX.lock().unwrap();
548        let flag = Arc::new(AtomicBool::new(false));
549        let moved_flag = flag.clone();
550
551        rcu_call(move || {
552            moved_flag.store(true, Ordering::SeqCst);
553        });
554
555        assert!(rcu_run_callbacks());
556        assert!(
557            flag.load(Ordering::SeqCst),
558            "Callback should have executed after rcu_run_callbacks()"
559        );
560        // Second step has no pending work.
561        assert!(!rcu_run_callbacks());
562        RCU_CONTROL_BLOCK.advancer_thread_state.store(ADVANCER_THREAD_SLEEPING, Ordering::SeqCst);
563    }
564
565    #[test]
566    fn test_rcu_advancer_thread_wake() {
567        let _lock = TEST_MUTEX.lock().unwrap();
568        let flag = Arc::new(AtomicBool::new(false));
569        let flag_clone = flag.clone();
570
571        let stop = Arc::new(AtomicBool::new(false));
572        let stop_clone = stop.clone();
573
574        let handle = std::thread::spawn(move || {
575            while !stop_clone.load(Ordering::Relaxed) {
576                rcu_advancer_wait_for_work();
577                rcu_run_callbacks();
578            }
579            rcu_run_callbacks();
580        });
581
582        rcu_call(move || {
583            flag_clone.store(true, Ordering::SeqCst);
584        });
585
586        // Wait for advancer thread to wake up and process callback.
587        let start = std::time::Instant::now();
588        while !flag.load(Ordering::SeqCst) {
589            assert!(
590                start.elapsed() < std::time::Duration::from_secs(5),
591                "Timed out waiting for advancer thread to process callback"
592            );
593            std::thread::sleep(std::time::Duration::from_millis(10));
594        }
595
596        stop.store(true, Ordering::Relaxed);
597        // Wake the thread if it went back to sleep so it can terminate.
598        rcu_advancer_wake();
599        handle.join().unwrap();
600        RCU_CONTROL_BLOCK.advancer_thread_state.store(ADVANCER_THREAD_SLEEPING, Ordering::SeqCst);
601    }
602
603    #[test]
604    fn test_rcu_synchronize_no_callbacks() {
605        let _lock = TEST_MUTEX.lock().unwrap();
606        let stop = Arc::new(AtomicBool::new(false));
607        let stop_clone = stop.clone();
608
609        let handle = std::thread::spawn(move || {
610            while !stop_clone.load(Ordering::Relaxed) {
611                rcu_advancer_wait_for_work();
612                rcu_run_callbacks();
613            }
614            rcu_run_callbacks();
615        });
616
617        // Calling rcu_synchronize() with no prior callbacks should wake the advancer,
618        // wait for the grace periods to complete, and return successfully.
619        rcu_synchronize();
620
621        stop.store(true, Ordering::Relaxed);
622        rcu_advancer_wake();
623        handle.join().unwrap();
624        RCU_CONTROL_BLOCK.advancer_thread_state.store(ADVANCER_THREAD_SLEEPING, Ordering::SeqCst);
625    }
626}