Skip to content

Commit 2adcf3e

Browse files
committed
Apply lifecycle lock to all middleware entities
Signed-off-by: Michael X. Grey <[email protected]>
1 parent f30a284 commit 2adcf3e

File tree

10 files changed

+50
-20
lines changed

10 files changed

+50
-20
lines changed

rclrs/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,21 @@ path = "src/lib.rs"
1414
# Please keep the list of dependencies alphabetically sorted,
1515
# and also state why each dependency is needed.
1616
[dependencies]
17-
# Needed for dynamically finding type support libraries
17+
# Needed for dynamically finding type support libraries
1818
ament_rs = { version = "0.2", optional = true }
19+
1920
# Needed for uploading documentation to docs.rs
2021
cfg-if = "1.0.0"
2122

2223
# Needed for clients
2324
futures = "0.3"
25+
26+
# Needed to create a global mutex for managing the lifecycles of middleware entities
27+
lazy_static = "1.4"
28+
2429
# Needed for dynamic messages
2530
libloading = { version = "0.8", optional = true }
31+
2632
# Needed for the Message trait, among others
2733
rosidl_runtime_rs = "0.4"
2834

rclrs/src/client.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rosidl_runtime_rs::Message;
99

1010
use crate::error::{RclReturnCode, ToResult};
1111
use crate::MessageCow;
12-
use crate::{rcl_bindings::*, RclrsError, NodeHandle};
12+
use crate::{rcl_bindings::*, RclrsError, NodeHandle, ENTITY_LIFECYCLE_MUTEX};
1313

1414
// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread
1515
// they are running in. Therefore, this type can be safely sent to another thread.
@@ -32,6 +32,7 @@ impl Drop for ClientHandle {
3232
fn drop(&mut self) {
3333
let rcl_client = self.rcl_client.get_mut().unwrap();
3434
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
35+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
3536
// SAFETY: No preconditions for this function
3637
unsafe {
3738
rcl_client_fini(rcl_client, &mut *rcl_node);
@@ -99,6 +100,7 @@ where
99100
// afterwards.
100101
{
101102
let mut rcl_node = node_handle.rcl_node.lock().unwrap();
103+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
102104
rcl_client_init(
103105
&mut rcl_client,
104106
&mut *rcl_node,

rclrs/src/context.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ use std::vec::Vec;
77
use crate::rcl_bindings::*;
88
use crate::{RclrsError, ToResult};
99

10+
lazy_static::lazy_static! {
11+
/// This is locked whenever initializing or dropping any middleware entity
12+
/// because we have found issues in RCL and some RMW implementations that
13+
/// make it unsafe to simultaneously initialize and/or drop various types of
14+
/// entities. It seems these C and C++ based libraries will regularly use
15+
/// unprotected global variables in their object initialization and cleanup.
16+
pub(crate) static ref ENTITY_LIFECYCLE_MUTEX: Mutex<()> = Mutex::new(());
17+
}
18+
1019
impl Drop for rcl_context_t {
1120
fn drop(&mut self) {
1221
unsafe {
1322
// The context may be invalid when rcl_init failed, e.g. because of invalid command
1423
// line arguments.
1524
// SAFETY: No preconditions for this function.
1625
if rcl_context_is_valid(self) {
26+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
1727
// SAFETY: These functions have no preconditions besides a valid rcl_context
1828
rcl_shutdown(self);
1929
rcl_context_fini(self);
@@ -102,17 +112,20 @@ impl Context {
102112
let mut rcl_init_options = options.into_rcl(allocator)?;
103113
// SAFETY: This function does not store the ephemeral init_options and c_args
104114
// pointers. Passing in a zero-initialized rcl_context is expected.
105-
let ret = rcl_init(
106-
c_args.len() as i32,
107-
if c_args.is_empty() {
108-
std::ptr::null()
109-
} else {
110-
c_args.as_ptr()
111-
},
112-
&rcl_init_options,
113-
&mut rcl_context,
114-
)
115-
.ok();
115+
let ret = {
116+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
117+
rcl_init(
118+
c_args.len() as i32,
119+
if c_args.is_empty() {
120+
std::ptr::null()
121+
} else {
122+
c_args.as_ptr()
123+
},
124+
&rcl_init_options,
125+
&mut rcl_context,
126+
)
127+
.ok()
128+
};
116129
// SAFETY: It's safe to pass in an initialized object.
117130
// Early return will not leak memory, because this is the last fini function.
118131
rcl_init_options_fini(&mut rcl_init_options).ok()?;

rclrs/src/node.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub use self::builder::*;
1313
pub use self::graph::*;
1414
use crate::rcl_bindings::*;
1515
use crate::{
16-
Client, ClientBase, Clock, Context, GuardCondition, ParameterBuilder, ParameterInterface,
16+
Client, ClientBase, Clock, Context, ENTITY_LIFECYCLE_MUTEX, GuardCondition, ParameterBuilder, ParameterInterface,
1717
ParameterVariant, Parameters, Publisher, QoSProfile, RclrsError, Service, ServiceBase,
1818
Subscription, SubscriptionBase, SubscriptionCallback, TimeSource, ContextHandle,
1919
};
@@ -78,6 +78,7 @@ impl Drop for NodeHandle {
7878
fn drop(&mut self) {
7979
let _context_lock = self.context_handle.rcl_context.lock().unwrap();
8080
let mut rcl_node = self.rcl_node.lock().unwrap();
81+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
8182
unsafe { rcl_node_fini(&mut *rcl_node) };
8283
}
8384
}

rclrs/src/node/builder.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex};
33

44
use crate::rcl_bindings::*;
55
use crate::{
6-
ClockType, Context, ContextHandle, Node, NodeHandle, ParameterInterface, QoSProfile, RclrsError, TimeSource, ToResult,
6+
ClockType, Context, ContextHandle, ENTITY_LIFECYCLE_MUTEX, Node, NodeHandle, ParameterInterface, QoSProfile, RclrsError, TimeSource, ToResult,
77
QOS_PROFILE_CLOCK,
88
};
99

@@ -270,6 +270,7 @@ impl NodeBuilder {
270270
// The strings and node options are copied by this function, so we don't need
271271
// to keep them alive.
272272
// The rcl_context has to be kept alive because it is co-owned by the node.
273+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
273274
rcl_node_init(
274275
&mut rcl_node,
275276
node_name.as_ptr(),

rclrs/src/publisher.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rosidl_runtime_rs::{Message, RmwMessage};
99
use crate::error::{RclrsError, ToResult};
1010
use crate::qos::QoSProfile;
1111
use crate::rcl_bindings::*;
12-
use crate::NodeHandle;
12+
use crate::{NodeHandle, ENTITY_LIFECYCLE_MUTEX};
1313

1414
mod loaned_message;
1515
pub use loaned_message::*;
@@ -28,6 +28,7 @@ impl Drop for PublisherHandle {
2828
unsafe {
2929
// SAFETY: No preconditions for this function (besides the arguments being valid).
3030
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
31+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
3132
rcl_publisher_fini(
3233
self.rcl_publisher.get_mut().unwrap(),
3334
&mut *rcl_node,
@@ -98,6 +99,7 @@ where
9899
// afterwards.
99100
// TODO: type support?
100101
let rcl_node = node_handle.rcl_node.lock().unwrap();
102+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
101103
rcl_publisher_init(
102104
&mut rcl_publisher,
103105
&*rcl_node,

rclrs/src/service.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rosidl_runtime_rs::Message;
77

88
use crate::error::{RclReturnCode, ToResult};
99
use crate::{rcl_bindings::*, MessageCow, RclrsError};
10-
use crate::NodeHandle;
10+
use crate::{NodeHandle, ENTITY_LIFECYCLE_MUTEX};
1111

1212
// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread
1313
// they are running in. Therefore, this type can be safely sent to another thread.
@@ -30,6 +30,7 @@ impl Drop for ServiceHandle {
3030
fn drop(&mut self) {
3131
let rcl_service = self.rcl_service.get_mut().unwrap();
3232
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
33+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
3334
// SAFETY: No preconditions for this function
3435
unsafe {
3536
rcl_service_fini(rcl_service, &mut *rcl_node);
@@ -100,6 +101,7 @@ where
100101
// The topic name and the options are copied by this function, so they can be dropped
101102
// afterwards.
102103
let rcl_node = node_handle.rcl_node.lock().unwrap();
104+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
103105
rcl_service_init(
104106
&mut rcl_service,
105107
&*rcl_node,

rclrs/src/subscription.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rosidl_runtime_rs::{Message, RmwMessage};
88

99
use crate::error::{RclReturnCode, ToResult};
1010
use crate::qos::QoSProfile;
11-
use crate::{rcl_bindings::*, RclrsError, NodeHandle};
11+
use crate::{rcl_bindings::*, RclrsError, NodeHandle, ENTITY_LIFECYCLE_MUTEX};
1212

1313
mod callback;
1414
mod message_info;
@@ -38,6 +38,7 @@ impl Drop for SubscriptionHandle {
3838
fn drop(&mut self) {
3939
let rcl_subscription = self.rcl_subscription.get_mut().unwrap();
4040
let mut rcl_node = self.node_handle.rcl_node.lock().unwrap();
41+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
4142
// SAFETY: No preconditions for this function (besides the arguments being valid).
4243
unsafe {
4344
rcl_subscription_fini(rcl_subscription, &mut *rcl_node);
@@ -114,6 +115,7 @@ where
114115
// afterwards.
115116
// TODO: type support?
116117
let rcl_node = node_handle.rcl_node.lock().unwrap();
118+
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
117119
rcl_subscription_init(
118120
&mut rcl_subscription,
119121
&*rcl_node,

rclrs/src/wait.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ impl WaitSet {
9696
let rcl_wait_set = unsafe {
9797
// SAFETY: Getting a zero-initialized value is always safe
9898
let mut rcl_wait_set = rcl_get_zero_initialized_wait_set();
99+
let mut rcl_context = context.handle.rcl_context.lock().unwrap();
99100
// SAFETY: We're passing in a zero-initialized wait set and a valid context.
100101
// There are no other preconditions.
101102
rcl_wait_set_init(
@@ -106,7 +107,7 @@ impl WaitSet {
106107
number_of_clients,
107108
number_of_services,
108109
number_of_events,
109-
&mut *context.handle.rcl_context.lock().unwrap(),
110+
&mut *rcl_context,
110111
rcutils_get_default_allocator(),
111112
)
112113
.ok()?;

rclrs/src/wait/guard_condition.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ impl GuardCondition {
110110
callback: Option<Box<dyn Fn() + Send + Sync>>,
111111
) -> Self {
112112
let rcl_guard_condition = {
113-
let mut rcl_context = context_handle.rcl_context.lock().unwrap();
114113
// SAFETY: Getting a zero initialized value is always safe
115114
let mut guard_condition = unsafe { rcl_get_zero_initialized_guard_condition() };
115+
let mut rcl_context = context_handle.rcl_context.lock().unwrap();
116116
unsafe {
117117
// SAFETY: The context must be valid, and the guard condition must be zero-initialized
118118
rcl_guard_condition_init(

0 commit comments

Comments
 (0)