r2r/
context.rs

1use std::{
2    ffi::{CStr, CString},
3    fmt::Debug,
4    ops::{Deref, DerefMut},
5    sync::{Arc, Mutex, OnceLock},
6};
7
8use crate::{error::*, log_guard};
9use r2r_rcl::*;
10
11/// A ROS context. Needed to create nodes etc.
12#[derive(Debug, Clone)]
13pub struct Context {
14    pub(crate) context_handle: Arc<Mutex<ContextHandle>>,
15}
16
17macro_rules! check_rcl_ret {
18    ($ret:expr) => {
19        if $ret != RCL_RET_OK as i32 {
20            let err_str = rcutils_get_error_string();
21            // c_char's definition is architecture specific
22            // https://github.com/fede1024/rust-rdkafka/issues/121#issuecomment-486578947
23            let str_ptr = &(err_str.str_) as *const std::os::raw::c_char;
24            let error_msg = CStr::from_ptr(str_ptr);
25            panic!("{}", error_msg.to_str().expect("to_str() call failed"));
26        }
27    };
28}
29
30unsafe impl Send for Context {}
31
32// Safety: Context is just a Arc<Mutex<..>> wrapper around ContextHandle
33// so it should be safe to access from different threads
34unsafe impl Sync for Context {}
35
36// Memory corruption (double free and others) was observed creating multiple
37// `Context` objects in a single thread
38//
39// To reproduce, run the tests from `tokio_testing` or `tokio_test_raw`
40// without this OnceLock
41
42static CONTEXT: OnceLock<Result<Context>> = OnceLock::new();
43
44impl Context {
45    /// Create a ROS context.
46    pub fn create() -> Result<Context> {
47        CONTEXT
48            .get_or_init(|| {
49                let mut ctx: Box<rcl_context_t> =
50                    unsafe { Box::new(rcl_get_zero_initialized_context()) };
51                // argc/v
52                let args = std::env::args()
53                    .map(|arg| CString::new(arg).unwrap())
54                    .collect::<Vec<CString>>();
55                let mut c_args = args
56                    .iter()
57                    .map(|arg| arg.as_ptr())
58                    .collect::<Vec<*const ::std::os::raw::c_char>>();
59                c_args.push(std::ptr::null());
60
61                let is_valid = unsafe {
62                    let allocator = rcutils_get_default_allocator();
63                    let mut init_options = rcl_get_zero_initialized_init_options();
64                    check_rcl_ret!(rcl_init_options_init(&mut init_options, allocator));
65                    check_rcl_ret!(rcl_init(
66                        (c_args.len() - 1) as ::std::os::raw::c_int,
67                        c_args.as_ptr(),
68                        &init_options,
69                        ctx.as_mut(),
70                    ));
71                    check_rcl_ret!(rcl_init_options_fini(&mut init_options as *mut _));
72                    rcl_context_is_valid(ctx.as_mut())
73                };
74
75                let logging_ok = unsafe {
76                    let _guard = log_guard();
77                    let ret = rcl_logging_configure(
78                        &ctx.as_ref().global_arguments,
79                        &rcutils_get_default_allocator(),
80                    );
81                    ret == RCL_RET_OK as i32
82                };
83
84                if is_valid && logging_ok {
85                    Ok(Context {
86                        context_handle: Arc::new(Mutex::new(ContextHandle(ctx))),
87                    })
88                } else {
89                    Err(Error::RCL_RET_ERROR) // TODO
90                }
91            })
92            .clone()
93    }
94
95    /// Check if the ROS context is valid.
96    ///
97    /// (This is abbreviated to rcl_ok() in the other bindings.)
98    pub fn is_valid(&self) -> bool {
99        let mut ctx = self.context_handle.lock().unwrap();
100        unsafe { rcl_context_is_valid(ctx.as_mut()) }
101    }
102}
103
104#[derive(Debug)]
105pub struct ContextHandle(Box<rcl_context_t>);
106
107impl Deref for ContextHandle {
108    type Target = Box<rcl_context_t>;
109
110    fn deref(&self) -> &Box<rcl_context_t> {
111        &self.0
112    }
113}
114
115impl DerefMut for ContextHandle {
116    fn deref_mut(&mut self) -> &mut Box<rcl_context_t> {
117        &mut self.0
118    }
119}
120
121impl Drop for ContextHandle {
122    fn drop(&mut self) {
123        // TODO: error handling? atleast probably need rcl_reset_error
124        unsafe {
125            rcl_shutdown(self.0.as_mut());
126            rcl_context_fini(self.0.as_mut());
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_context_drop() {
137        {
138            let ctx = Context::create().unwrap();
139            assert!(ctx.is_valid());
140        }
141        {
142            let ctx = Context::create().unwrap();
143            assert!(ctx.is_valid());
144        }
145    }
146}