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}