Skip to content

Commit 5abbb34

Browse files
committed
Update documentation and safety info on rcl entity lifecycles
Signed-off-by: Michael X. Grey <mxgrey@intrinsic.ai>
1 parent 4caa208 commit 5abbb34

File tree

9 files changed

+130
-70
lines changed

9 files changed

+130
-70
lines changed

rclrs/src/client.rs

+19-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ use crate::{rcl_bindings::*, NodeHandle, RclrsError, ENTITY_LIFECYCLE_MUTEX};
1515
// they are running in. Therefore, this type can be safely sent to another thread.
1616
unsafe impl Send for rcl_client_t {}
1717

18-
/// Internal struct used by clients.
18+
/// Manage the lifecycle of an [`rcl_client_t`], including managing its dependencies
19+
/// on [`rcl_node_t`] and [`rcl_context_t`] by ensuring that these dependencies are
20+
/// [dropped after][1] the [`rcl_client_t`].
21+
///
22+
/// [1] https://doc.rust-lang.org/reference/destructors.html
1923
pub struct ClientHandle {
2024
rcl_client: Mutex<rcl_client_t>,
2125
node_handle: Arc<NodeHandle>,
@@ -33,7 +37,8 @@ impl Drop for ClientHandle {
3337
let rcl_client = self.rcl_client.get_mut().unwrap();
3438
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
3539
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
36-
// SAFETY: No preconditions for this function
40+
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
41+
// global variables in the rmw implementation being unsafely modified during cleanup.
3742
unsafe {
3843
rcl_client_fini(rcl_client, &mut *rcl_node);
3944
}
@@ -93,14 +98,18 @@ where
9398
// SAFETY: No preconditions for this function.
9499
let client_options = unsafe { rcl_client_get_default_options() };
95100

96-
unsafe {
97-
// SAFETY: The rcl_client is zero-initialized as expected by this function.
98-
// The rcl_node is kept alive because it is co-owned by the client.
99-
// The topic name and the options are copied by this function, so they can be dropped
100-
// afterwards.
101-
{
102-
let rcl_node = node_handle.rcl_node.lock().unwrap();
103-
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
101+
{
102+
let rcl_node = node_handle.rcl_node.lock().unwrap();
103+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
104+
105+
// SAFETY:
106+
// * The rcl_client was zero-initialized as expected by this function.
107+
// * The rcl_node is kept alive by the NodeHandle because it is a dependency of the client.
108+
// * The topic name and the options are copied by this function, so they can be dropped
109+
// afterwards.
110+
// * The entity lifecycle mutex is locked to protect against the risk of global
111+
// variables in the rmw implementation being unsafely modified during initialization.
112+
unsafe {
104113
rcl_client_init(
105114
&mut rcl_client,
106115
&*rcl_node,

rclrs/src/context.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ impl Drop for rcl_context_t {
1919
unsafe {
2020
// The context may be invalid when rcl_init failed, e.g. because of invalid command
2121
// line arguments.
22-
// SAFETY: No preconditions for this function.
22+
23+
// SAFETY: No preconditions for rcl_context_is_valid.
2324
if rcl_context_is_valid(self) {
2425
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
25-
// SAFETY: These functions have no preconditions besides a valid rcl_context
26+
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
27+
// global variables in the rmw implementation being unsafely modified during cleanup.
2628
rcl_shutdown(self);
2729
rcl_context_fini(self);
2830
}
@@ -50,10 +52,10 @@ pub struct Context {
5052
pub(crate) handle: Arc<ContextHandle>,
5153
}
5254

53-
/// This struct manages the lifetime and access to the rcl context. It will also
55+
/// This struct manages the lifetime and access to the [`rcl_context_t`]. It will also
5456
/// account for the lifetimes of any dependencies, if we need to add
5557
/// dependencies in the future (currently there are none). It is not strictly
56-
/// necessary to decompose `Context` and `ContextHandle` like this, but we are
58+
/// necessary to decompose [`Context`] and [`ContextHandle`] like this, but we are
5759
/// doing it to be consistent with the lifecycle management of other rcl
5860
/// bindings in this library.
5961
pub(crate) struct ContextHandle {
@@ -108,8 +110,11 @@ impl Context {
108110
// SAFETY: No preconditions for this function.
109111
let allocator = rcutils_get_default_allocator();
110112
let mut rcl_init_options = options.into_rcl(allocator)?;
111-
// SAFETY: This function does not store the ephemeral init_options and c_args
112-
// pointers. Passing in a zero-initialized rcl_context is expected.
113+
// SAFETY:
114+
// * This function does not store the ephemeral init_options and c_args pointers.
115+
// * Passing in a zero-initialized rcl_context is mandatory.
116+
// * The entity lifecycle mutex is locked to protect against the risk of global variables
117+
// in the rmw implementation being unsafely modified during initialization.
113118
let ret = {
114119
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
115120
rcl_init(

rclrs/src/node.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ pub struct Node {
6868
pub(crate) handle: Arc<NodeHandle>,
6969
}
7070

71-
/// This struct manages the lifetime of the rcl node, and accounts for its
72-
/// dependency on the lifetime of its context.
71+
/// This struct manages the lifetime of an [`rcl_node_t`], and accounts for its
72+
/// dependency on the lifetime of its [`rcl_context_t`] by ensuring that this
73+
/// dependency is [dropped after][1] the [`rcl_node_t`].
74+
///
75+
/// [1] https://doc.rust-lang.org/reference/destructors.html
7376
pub(crate) struct NodeHandle {
7477
pub(crate) rcl_node: Mutex<rcl_node_t>,
7578
pub(crate) context_handle: Arc<ContextHandle>,
@@ -80,6 +83,8 @@ impl Drop for NodeHandle {
8083
let _context_lock = self.context_handle.rcl_context.lock().unwrap();
8184
let mut rcl_node = self.rcl_node.lock().unwrap();
8285
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
86+
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
87+
// global variables in the rmw implementation being unsafely modified during cleanup.
8388
unsafe { rcl_node_fini(&mut *rcl_node) };
8489
}
8590
}

rclrs/src/node/builder.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,12 @@ impl NodeBuilder {
266266
// SAFETY: Getting a zero-initialized value is always safe.
267267
let mut rcl_node = unsafe { rcl_get_zero_initialized_node() };
268268
unsafe {
269-
// SAFETY: The rcl_node is zero-initialized as expected by this function.
270-
// The strings and node options are copied by this function, so we don't need
271-
// to keep them alive.
272-
// The rcl_context has to be kept alive because it is co-owned by the node.
269+
// SAFETY:
270+
// * The rcl_node is zero-initialized as mandated by this function.
271+
// * The strings and node options are copied by this function, so we don't need to keep them alive.
272+
// * The rcl_context is kept alive by the ContextHandle because it is a dependency of the node.
273+
// * The entity lifecycle mutex is locked to protect against the risk of
274+
// global variables in the rmw implementation being unsafely modified during cleanup.
273275
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
274276
rcl_node_init(
275277
&mut rcl_node,

rclrs/src/publisher.rs

+27-17
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,23 @@ pub use loaned_message::*;
1818
// they are running in. Therefore, this type can be safely sent to another thread.
1919
unsafe impl Send for rcl_publisher_t {}
2020

21+
/// Manage the lifecycle of an [`rcl_publisher_t`], including managing its dependencies
22+
/// on [`rcl_node_t`] and [`rcl_context_t`] by ensuring that these dependencies are
23+
/// [dropped after][1] the [`rcl_publisher_t`].
24+
///
25+
/// [1] https://doc.rust-lang.org/reference/destructors.html
2126
struct PublisherHandle {
2227
rcl_publisher: Mutex<rcl_publisher_t>,
2328
node_handle: Arc<NodeHandle>,
2429
}
2530

2631
impl Drop for PublisherHandle {
2732
fn drop(&mut self) {
33+
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
34+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
35+
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
36+
// global variables in the rmw implementation being unsafely modified during cleanup.
2837
unsafe {
29-
// SAFETY: No preconditions for this function (besides the arguments being valid).
30-
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
31-
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
3238
rcl_publisher_fini(self.rcl_publisher.get_mut().unwrap(), &mut *rcl_node);
3339
}
3440
}
@@ -89,22 +95,26 @@ where
8995
// SAFETY: No preconditions for this function.
9096
let mut publisher_options = unsafe { rcl_publisher_get_default_options() };
9197
publisher_options.qos = qos.into();
92-
unsafe {
93-
// SAFETY: The rcl_publisher is zero-initialized as expected by this function.
94-
// The rcl_node is kept alive because it is co-owned by the subscription.
95-
// The topic name and the options are copied by this function, so they can be dropped
96-
// afterwards.
97-
// TODO: type support?
98+
99+
{
98100
let rcl_node = node_handle.rcl_node.lock().unwrap();
99101
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
100-
rcl_publisher_init(
101-
&mut rcl_publisher,
102-
&*rcl_node,
103-
type_support_ptr,
104-
topic_c_string.as_ptr(),
105-
&publisher_options,
106-
)
107-
.ok()?;
102+
unsafe {
103+
// SAFETY:
104+
// * The rcl_publisher is zero-initialized as mandated by this function.
105+
// * The rcl_node is kept alive by the NodeHandle because it is a dependency of the publisher.
106+
// * The topic name and the options are copied by this function, so they can be dropped afterwards.
107+
// * The entity lifecycle mutex is locked to protect against the risk of global
108+
// variables in the rmw implementation being unsafely modified during cleanup.
109+
rcl_publisher_init(
110+
&mut rcl_publisher,
111+
&*rcl_node,
112+
type_support_ptr,
113+
topic_c_string.as_ptr(),
114+
&publisher_options,
115+
)
116+
.ok()?;
117+
}
108118
}
109119

110120
Ok(Self {

rclrs/src/service.rs

+25-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ use crate::{NodeHandle, ENTITY_LIFECYCLE_MUTEX};
1313
// they are running in. Therefore, this type can be safely sent to another thread.
1414
unsafe impl Send for rcl_service_t {}
1515

16-
/// Internal struct used by services.
16+
/// Manage the lifecycle of an [`rcl_service_t`], including managing its dependencies
17+
/// on [`rcl_node_t`] and [`rcl_context_t`] by ensuring that these dependencies are
18+
/// [dropped after][1] the [`rcl_service_t`].
19+
///
20+
/// [1] https://doc.rust-lang.org/reference/destructors.html
1721
pub struct ServiceHandle {
1822
rcl_service: Mutex<rcl_service_t>,
1923
node_handle: Arc<NodeHandle>,
@@ -31,7 +35,8 @@ impl Drop for ServiceHandle {
3135
let rcl_service = self.rcl_service.get_mut().unwrap();
3236
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
3337
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
34-
// SAFETY: No preconditions for this function
38+
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
39+
// global variables in the rmw implementation being unsafely modified during cleanup.
3540
unsafe {
3641
rcl_service_fini(rcl_service, &mut *rcl_node);
3742
}
@@ -95,21 +100,26 @@ where
95100
// SAFETY: No preconditions for this function.
96101
let service_options = unsafe { rcl_service_get_default_options() };
97102

98-
unsafe {
99-
// SAFETY: The rcl_service is zero-initialized as expected by this function.
100-
// The rcl_node is kept alive because it is co-owned by the service.
101-
// The topic name and the options are copied by this function, so they can be dropped
102-
// afterwards.
103+
{
103104
let rcl_node = node_handle.rcl_node.lock().unwrap();
104105
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
105-
rcl_service_init(
106-
&mut rcl_service,
107-
&*rcl_node,
108-
type_support,
109-
topic_c_string.as_ptr(),
110-
&service_options as *const _,
111-
)
112-
.ok()?;
106+
unsafe {
107+
// SAFETY:
108+
// * The rcl_service is zero-initialized as mandated by this function.
109+
// * The rcl_node is kept alive by the NodeHandle it is a dependency of the service.
110+
// * The topic name and the options are copied by this function, so they can be dropped
111+
// afterwards.
112+
// * The entity lifecycle mutex is locked to protect against the risk of global
113+
// variables in the rmw implementation being unsafely modified during initialization.
114+
rcl_service_init(
115+
&mut rcl_service,
116+
&*rcl_node,
117+
type_support,
118+
topic_c_string.as_ptr(),
119+
&service_options as *const _,
120+
)
121+
.ok()?;
122+
}
113123
}
114124

115125
let handle = Arc::new(ServiceHandle {

rclrs/src/subscription.rs

+25-16
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ pub use readonly_loaned_message::*;
2121
// they are running in. Therefore, this type can be safely sent to another thread.
2222
unsafe impl Send for rcl_subscription_t {}
2323

24-
/// Internal struct used by subscriptions.
24+
/// Manage the lifecycle of an [`rcl_subscription_t`], including managing its dependencies
25+
/// on [`rcl_node_t`] and [`rcl_context_t`] by ensuring that these dependencies are
26+
/// [dropped after][1] the [`rcl_subscription_t`].
27+
///
28+
/// [1] https://doc.rust-lang.org/reference/destructors.html
2529
pub struct SubscriptionHandle {
2630
rcl_subscription: Mutex<rcl_subscription_t>,
2731
node_handle: Arc<NodeHandle>,
@@ -39,7 +43,8 @@ impl Drop for SubscriptionHandle {
3943
let rcl_subscription = self.rcl_subscription.get_mut().unwrap();
4044
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
4145
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
42-
// SAFETY: No preconditions for this function (besides the arguments being valid).
46+
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
47+
// global variables in the rmw implementation being unsafely modified during cleanup.
4348
unsafe {
4449
rcl_subscription_fini(rcl_subscription, &mut *rcl_node);
4550
}
@@ -108,22 +113,26 @@ where
108113
// SAFETY: No preconditions for this function.
109114
let mut subscription_options = unsafe { rcl_subscription_get_default_options() };
110115
subscription_options.qos = qos.into();
111-
unsafe {
112-
// SAFETY: The rcl_subscription is zero-initialized as expected by this function.
113-
// The rcl_node is kept alive because it is co-owned by the subscription.
114-
// The topic name and the options are copied by this function, so they can be dropped
115-
// afterwards.
116-
// TODO: type support?
116+
117+
{
117118
let rcl_node = node_handle.rcl_node.lock().unwrap();
118119
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
119-
rcl_subscription_init(
120-
&mut rcl_subscription,
121-
&*rcl_node,
122-
type_support,
123-
topic_c_string.as_ptr(),
124-
&subscription_options,
125-
)
126-
.ok()?;
120+
unsafe {
121+
// SAFETY:
122+
// * The rcl_subscription is zero-initialized as mandated by this function.
123+
// * The rcl_node is kept alive by the NodeHandle because it is a dependency of the subscription.
124+
// * The topic name and the options are copied by this function, so they can be dropped afterwards.
125+
// * The entity lifecycle mutex is locked to protect against the risk of global
126+
// variables in the rmw implementation being unsafely modified during cleanup.
127+
rcl_subscription_init(
128+
&mut rcl_subscription,
129+
&*rcl_node,
130+
type_support,
131+
topic_c_string.as_ptr(),
132+
&subscription_options,
133+
)
134+
.ok()?;
135+
}
127136
}
128137

129138
let handle = Arc::new(SubscriptionHandle {

rclrs/src/wait.rs

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ mod guard_condition;
2828
use exclusivity_guard::*;
2929
pub use guard_condition::*;
3030

31+
/// Manage the lifecycle of an [`rcl_wait_set_t`], including managing its dependency
32+
/// on [`rcl_context_t`] by ensuring that this dependency is [dropped after][1] the
33+
/// [`rcl_wait_set_t`].
34+
///
35+
/// [1] https://doc.rust-lang.org/reference/destructors.html
3136
struct WaitSetHandle {
3237
rcl_wait_set: rcl_wait_set_t,
3338
// Used to ensure the context is alive while the wait set is alive.

rclrs/src/wait/guard_condition.rs

+5
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ pub struct GuardCondition {
5252
pub(crate) in_use_by_wait_set: Arc<AtomicBool>,
5353
}
5454

55+
/// Manage the lifecycle of an [`rcl_guard_condition_t`], including managing its dependency
56+
/// on [`rcl_context_t`] by ensuring that this dependency is [dropped after][1] the
57+
/// [`rcl_guard_condition_t`].
58+
///
59+
/// [1] https://doc.rust-lang.org/reference/destructors.html
5560
pub(crate) struct GuardConditionHandle {
5661
pub(crate) rcl_guard_condition: Mutex<rcl_guard_condition_t>,
5762
/// Keep the context alive for the whole lifecycle of the guard condition

0 commit comments

Comments
 (0)