do_not_use_testing_rclrs/
context.rs

1use std::ffi::CString;
2use std::os::raw::c_char;
3use std::string::String;
4use std::sync::{Arc, Mutex};
5use std::vec::Vec;
6
7use crate::rcl_bindings::*;
8use crate::{RclrsError, ToResult};
9
10impl Drop for rcl_context_t {
11    fn drop(&mut self) {
12        unsafe {
13            // The context may be invalid when rcl_init failed, e.g. because of invalid command
14            // line arguments.
15            // SAFETY: No preconditions for this function.
16            if rcl_context_is_valid(self) {
17                // SAFETY: These functions have no preconditions besides a valid rcl_context
18                rcl_shutdown(self);
19                rcl_context_fini(self);
20            }
21        }
22    }
23}
24
25// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread
26// they are running in. Therefore, this type can be safely sent to another thread.
27unsafe impl Send for rcl_context_t {}
28
29/// Shared state between nodes and similar entities.
30///
31/// It is possible, but not usually necessary, to have several contexts in an application.
32///
33/// Ownership of the context is shared by the `Context` itself and all nodes created from it.
34///
35/// # Details
36/// A context stores, among other things
37/// - command line arguments (used for e.g. name remapping)
38/// - middleware-specific data, e.g. the domain participant in DDS
39/// - the allocator used (left as the default by `rclrs`)
40///
41pub struct Context {
42    pub(crate) rcl_context_mtx: Arc<Mutex<rcl_context_t>>,
43}
44
45impl Context {
46    /// Creates a new context.
47    ///
48    /// Usually, this would be called with `std::env::args()`, analogously to `rclcpp::init()`.
49    /// See also the official "Passing ROS arguments to nodes via the command-line" tutorial.
50    ///
51    /// Creating a context can fail in case the args contain invalid ROS arguments.
52    ///
53    /// # Example
54    /// ```
55    /// # use rclrs::Context;
56    /// assert!(Context::new([]).is_ok());
57    /// let invalid_remapping = ["--ros-args", "-r", ":=:*/]"].map(String::from);
58    /// assert!(Context::new(invalid_remapping).is_err());
59    /// ```
60    pub fn new(args: impl IntoIterator<Item = String>) -> Result<Self, RclrsError> {
61        // SAFETY: Getting a zero-initialized value is always safe
62        let mut rcl_context = unsafe { rcl_get_zero_initialized_context() };
63        let cstring_args: Vec<CString> = args
64            .into_iter()
65            .map(|arg| {
66                CString::new(arg.as_str()).map_err(|err| RclrsError::StringContainsNul {
67                    err,
68                    s: arg.clone(),
69                })
70            })
71            .collect::<Result<_, _>>()?;
72        // Vector of pointers into cstring_args
73        let c_args: Vec<*const c_char> = cstring_args.iter().map(|arg| arg.as_ptr()).collect();
74        unsafe {
75            // SAFETY: No preconditions for this function.
76            let allocator = rcutils_get_default_allocator();
77            // SAFETY: Getting a zero-initialized value is always safe.
78            let mut rcl_init_options = rcl_get_zero_initialized_init_options();
79            // SAFETY: Passing in a zero-initialized value is expected.
80            // In the case where this returns not ok, there's nothing to clean up.
81            rcl_init_options_init(&mut rcl_init_options, allocator).ok()?;
82            // SAFETY: This function does not store the ephemeral init_options and c_args
83            // pointers. Passing in a zero-initialized rcl_context is expected.
84            let ret = rcl_init(
85                c_args.len() as i32,
86                if c_args.is_empty() {
87                    std::ptr::null()
88                } else {
89                    c_args.as_ptr()
90                },
91                &rcl_init_options,
92                &mut rcl_context,
93            )
94            .ok();
95            // SAFETY: It's safe to pass in an initialized object.
96            // Early return will not leak memory, because this is the last fini function.
97            rcl_init_options_fini(&mut rcl_init_options).ok()?;
98            // Move the check after the last fini()
99            ret?;
100        }
101        Ok(Self {
102            rcl_context_mtx: Arc::new(Mutex::new(rcl_context)),
103        })
104    }
105
106    /// Checks if the context is still valid.
107    ///
108    /// This will return `false` when a signal has caused the context to shut down (currently
109    /// unimplemented).
110    pub fn ok(&self) -> bool {
111        // This will currently always return true, but once we have a signal handler, the signal
112        // handler could call `rcl_shutdown()`, hence making the context invalid.
113        let rcl_context = &mut *self.rcl_context_mtx.lock().unwrap();
114        // SAFETY: No preconditions for this function.
115        unsafe { rcl_context_is_valid(rcl_context) }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    fn assert_send<T: Send>() {}
124    fn assert_sync<T: Sync>() {}
125
126    #[test]
127    fn context_is_send_and_sync() {
128        assert_send::<Context>();
129        assert_sync::<Context>();
130    }
131
132    #[test]
133    fn test_create_context() -> Result<(), RclrsError> {
134        // If the context fails to be created, this will cause a panic
135        let _ = Context::new(vec![])?;
136        Ok(())
137    }
138
139    #[test]
140    fn test_context_ok() -> Result<(), RclrsError> {
141        // If the context fails to be created, this will cause a panic
142        let created_context = Context::new(vec![]).unwrap();
143        assert!(created_context.ok());
144
145        Ok(())
146    }
147}