Skip to main content

urcu/rcu/
cleanup.rs

1//! This module implements a cleaner thread.
2//!
3//! The goal is to allow any thread (registered or not) to execute
4//! a callback on a registered thread. It is currently only used for
5//! cleaning up [`RcuRef`].
6//!
7//! [`RcuRef`]: crate::rcu::reference::RcuRef
8
9use std::sync::mpsc::{Receiver, Sender};
10use std::sync::{Once, RwLock};
11use std::thread::JoinHandle;
12
13use crate::rcu::context::RcuContext;
14use crate::rcu::flavor::RcuFlavor;
15
16/// Defines the cleanup callback signature.
17pub type RcuCleanup<C> = Box<dyn FnOnce(&C) + Send + 'static>;
18
19/// Defines the cleanup callback signature.
20pub type RcuCleanupMut<C> = Box<dyn FnOnce(&mut C) + Send + 'static>;
21
22type ContextFn<C> = Box<dyn FnOnce() -> C + Send>;
23
24enum Command<C> {
25    Execute(RcuCleanup<C>),
26    ExecuteMut(RcuCleanupMut<C>),
27    Barrier(Sender<()>),
28    Shutdown,
29}
30
31struct Thread<C> {
32    commands: Receiver<Command<C>>,
33}
34
35impl<C> Thread<C>
36where
37    C: 'static,
38{
39    fn start(context: ContextFn<C>, commands: Receiver<Command<C>>) -> JoinHandle<()> {
40        std::thread::Builder::new()
41            .name(format!(
42                "urcu::cleanup::{}",
43                std::any::type_name::<C>()
44                    .split("::")
45                    .last()
46                    .unwrap()
47                    .replace("RcuContext", "")
48                    .to_lowercase()
49            ))
50            .spawn(move || Self { commands }.run(context))
51            .unwrap()
52    }
53
54    fn run(self, context: ContextFn<C>) {
55        log::debug!("launching cleanup thread");
56
57        let mut context = context();
58
59        loop {
60            match self.commands.recv() {
61                Ok(Command::Execute(callback)) => callback(&context),
62                Ok(Command::ExecuteMut(callback)) => callback(&mut context),
63                Ok(Command::Shutdown) => break,
64                Ok(Command::Barrier(sender)) => {
65                    if let Err(e) = sender.send(()) {
66                        log::error!("failed to execute cleanup barrier: {:?}", e);
67                    }
68                }
69                Err(e) => {
70                    log::error!("failed to get cleanup command: {:?}", e);
71                    break;
72                }
73            }
74        }
75
76        log::debug!("shutting down cleanup thread");
77    }
78}
79
80struct ThreadHandle<C> {
81    thread: Option<JoinHandle<()>>,
82    callbacks: Sender<Command<C>>,
83}
84
85impl<C> ThreadHandle<C>
86where
87    C: RcuContext + 'static,
88{
89    fn create(instance: &RwLock<Option<Self>>, context: ContextFn<C>) -> RcuCleaner<C> {
90        RcuCleaner(
91            instance
92                .write()
93                .unwrap()
94                .get_or_insert_with(|| {
95                    let (tx, rx) = std::sync::mpsc::channel();
96
97                    Self {
98                        thread: Some(Thread::start(context, rx)),
99                        callbacks: tx,
100                    }
101                })
102                .callbacks
103                .clone(),
104        )
105    }
106
107    fn try_get(instance: &RwLock<Option<Self>>) -> Option<RcuCleaner<C>> {
108        instance
109            .read()
110            .unwrap()
111            .as_ref()
112            .map(|handle| RcuCleaner(handle.callbacks.clone()))
113    }
114
115    fn get(instance: &RwLock<Option<Self>>, context: ContextFn<C>) -> RcuCleaner<C> {
116        Self::try_get(instance).unwrap_or_else(|| Self::create(instance, context))
117    }
118
119    fn delete(instance: &RwLock<Option<Self>>) {
120        instance.write().unwrap().take();
121    }
122}
123
124impl<C> Drop for ThreadHandle<C> {
125    fn drop(&mut self) {
126        log::trace!("sending shutdown command");
127
128        if let Err(e) = self.callbacks.send(Command::Shutdown) {
129            log::error!("failed to send shutdown command: {:?}", e);
130            return;
131        }
132
133        if let Some(handle) = self.thread.take() {
134            if let Err(e) = handle.join() {
135                log::error!("failed to join cleanup thread: {:?}", e);
136            }
137        }
138    }
139}
140
141pub struct RcuCleaner<C>(Sender<Command<C>>);
142
143impl<C> RcuCleaner<C> {
144    pub fn send(&self, callback: RcuCleanup<C>) -> &Self {
145        let command = Command::Execute(callback);
146        if let Err(e) = self.0.send(command) {
147            log::error!("failed to send execute command: {:?}", e);
148        }
149
150        self
151    }
152
153    pub fn send_mut(&self, callback: RcuCleanupMut<C>) -> &Self {
154        let command = Command::ExecuteMut(callback);
155        if let Err(e) = self.0.send(command) {
156            log::error!("failed to send execute command: {:?}", e);
157        }
158
159        self
160    }
161
162    pub fn barrier(&self) -> &Self {
163        let (tx, rx) = std::sync::mpsc::channel();
164
165        let command = Command::Barrier(tx);
166        if let Err(e) = self.0.send(command) {
167            log::error!("failed to send barrier command: {:?}", e);
168        } else if let Err(e) = rx.recv() {
169            log::error!("failed to wait for barrier: {:?}", e);
170        } else {
171            log::trace!("finished barrier command");
172        }
173
174        self
175    }
176}
177
178macro_rules! impl_cleanup_for_context {
179    ($flavor:ident, $context:ident) => {
180        static REGISTER_ATEXIT: Once = Once::new();
181        static INSTANCE: RwLock<Option<ThreadHandle<$context<true, true>>>> = RwLock::new(None);
182
183        impl RcuCleaner<$flavor> {
184            extern "C" fn delete() {
185                ThreadHandle::<$context<true, true>>::delete(&INSTANCE);
186            }
187
188            pub fn get() -> RcuCleaner<$context<true, true>> {
189                REGISTER_ATEXIT.call_once(|| unsafe {
190                    assert_eq!(libc::atexit(Self::delete), 0);
191                });
192
193                let context = Box::new(|| {
194                    $flavor::rcu_context_builder()
195                        .with_read_context()
196                        .with_defer_context()
197                        .register_thread()
198                        .unwrap()
199                });
200
201                ThreadHandle::<$context<true, true>>::get(&INSTANCE, context)
202            }
203        }
204    };
205}
206
207#[cfg(feature = "flavor-bp")]
208mod bp {
209    use super::*;
210
211    use crate::rcu::context::RcuContextBp;
212    use crate::rcu::flavor::RcuFlavorBp;
213
214    impl_cleanup_for_context!(RcuFlavorBp, RcuContextBp);
215}
216
217#[cfg(feature = "flavor-mb")]
218mod mb {
219    use super::*;
220
221    use crate::rcu::context::RcuContextMb;
222    use crate::rcu::flavor::RcuFlavorMb;
223
224    impl_cleanup_for_context!(RcuFlavorMb, RcuContextMb);
225}
226
227#[cfg(feature = "flavor-memb")]
228mod memb {
229    use super::*;
230
231    use crate::rcu::context::RcuContextMemb;
232    use crate::rcu::flavor::RcuFlavorMemb;
233
234    impl_cleanup_for_context!(RcuFlavorMemb, RcuContextMemb);
235}
236
237#[cfg(feature = "flavor-qsbr")]
238mod qsbr {
239    use super::*;
240
241    use crate::rcu::context::RcuContextQsbr;
242    use crate::rcu::flavor::RcuFlavorQsbr;
243
244    impl_cleanup_for_context!(RcuFlavorQsbr, RcuContextQsbr);
245}