Skip to content

Commit 09f5664

Browse files
authored
Fix logging lifecycles and clean up tests
Signed-off-by: Michael X. Grey <[email protected]>
1 parent ae966a4 commit 09f5664

File tree

8 files changed

+90
-46
lines changed

8 files changed

+90
-46
lines changed

rclrs/src/context.rs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
vec::Vec,
77
};
88

9-
use crate::{rcl_bindings::*, RclrsError, ToResult};
9+
use crate::{rcl_bindings::*, LoggingLifecycle, RclrsError, ToResult};
1010

1111
/// This is locked whenever initializing or dropping any middleware entity
1212
/// because we have found issues in RCL and some RMW implementations that
@@ -34,10 +34,6 @@ impl Drop for rcl_context_t {
3434
// SAFETY: The entity lifecycle mutex is locked to protect against the risk of
3535
// global variables in the rmw implementation being unsafely modified during cleanup.
3636
rcl_shutdown(self);
37-
38-
// SAFETY: No preconditions for rcl_logging_fini
39-
rcl_logging_fini();
40-
4137
rcl_context_fini(self);
4238
}
4339
}
@@ -76,6 +72,10 @@ pub struct Context {
7672
/// bindings in this library.
7773
pub(crate) struct ContextHandle {
7874
pub(crate) rcl_context: Mutex<rcl_context_t>,
75+
/// This ensures that logging does not get cleaned up until after this ContextHandle
76+
/// has dropped.
77+
#[allow(unused)]
78+
logging: Arc<LoggingLifecycle>,
7979
}
8080

8181
impl Context {
@@ -150,28 +150,17 @@ impl Context {
150150
rcl_init_options_fini(&mut rcl_init_options).ok()?;
151151
// Move the check after the last fini()
152152
ret?;
153+
}
153154

154-
// TODO: "Auto set-up logging" is forced but should be configurable as per rclcpp and rclpy
155-
// SAFETY:
156-
// * Lock the mutex as we cannot guarantee that rcl_* functions are protecting their global variables
157-
// * Context is validated before we reach this point
158-
// * No other preconditions for calling this function
159-
let ret = {
160-
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
161-
// Note: shadowing the allocator above. The rcutils_get_default_allocator provides us with mechanisms for allocating and freeing
162-
// memory, e.g. calloc/free. As these are function pointers and will be the same each time we call the method, it is safe to
163-
// perform this shadowing
164-
// Alternate is to call rcl_init_options_get_allocator however, we've freed the allocator above and restructuring the call
165-
// sequence is unnecessarily complex
166-
let allocator = rcutils_get_default_allocator();
155+
// TODO: "Auto set-up logging" is forced but should be configurable as per rclcpp and rclpy
156+
// SAFETY: We created this context a moment ago and verified that it is valid.
157+
// No other conditions are needed.
158+
let logging = unsafe { LoggingLifecycle::configure(&rcl_context)? };
167159

168-
rcl_logging_configure(&rcl_context.global_arguments, &allocator).ok()
169-
};
170-
ret?
171-
}
172160
Ok(Self {
173161
handle: Arc::new(ContextHandle {
174162
rcl_context: Mutex::new(rcl_context),
163+
logging,
175164
}),
176165
})
177166
}

rclrs/src/logging.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
use std::{ffi::CString, sync::Mutex, time::Duration};
77

8-
use crate::rcl_bindings::*;
8+
use crate::{rcl_bindings::*, ENTITY_LIFECYCLE_MUTEX};
99

10-
// Used to protect calls to rcl/rcutils in case those functions manipulate global variables
11-
static LOG_GUARD: Mutex<()> = Mutex::new(());
10+
mod logging_configuration;
11+
pub(crate) use logging_configuration::*;
1212

1313
/// Calls the underlying rclutils logging function
1414
/// Don't call this directly, use the logging macros instead.
@@ -39,8 +39,8 @@ pub fn log(msg: &str, logger_name: &str, file: &str, line: u32, severity: LogSev
3939
.expect("Valid c style string required, e.g. check for extraneous null characters");
4040
let severity = severity.to_native();
4141

42-
let _guard = LOG_GUARD.lock().unwrap();
43-
// SAFETY: Global variables are protected via LOG_GUARD, no other preconditions are required
42+
let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
43+
// SAFETY: Global variables are protected via ENTITY_LIFECYCLE_MUTEX, no other preconditions are required
4444
unsafe {
4545
rcutils_log(
4646
&location,
@@ -200,7 +200,7 @@ impl LogConditions {
200200
/// use std::env;
201201
///
202202
/// let context = rclrs::Context::new(env::args()).unwrap();
203-
/// let node = rclrs::Node::new(&context, "test_node").unwrap();
203+
/// let node = rclrs::Node::new(&context, "log_example_node").unwrap();
204204
///
205205
/// log_debug!(&node.name(), "Simple message");
206206
/// let some_variable = 43;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::sync::{Mutex, Weak, LazyLock, Arc};
2+
3+
use crate::{
4+
RclrsError, ENTITY_LIFECYCLE_MUTEX, ToResult,
5+
rcl_bindings::{
6+
rcl_context_t, rcl_arguments_t, rcutils_get_default_allocator,
7+
rcl_logging_configure, rcl_logging_fini,
8+
},
9+
};
10+
11+
12+
struct LoggingConfiguration {
13+
lifecycle: Mutex<Weak<LoggingLifecycle>>,
14+
}
15+
16+
pub(crate) struct LoggingLifecycle;
17+
18+
impl LoggingLifecycle {
19+
fn new(args: &rcl_arguments_t) -> Result<Self, RclrsError> {
20+
// SAFETY:
21+
// * Lock the mutex as we cannot guarantee that rcl_* functions are protecting their global variables
22+
// * This is only called by Self::configure, which requires that a valid context was passed to it
23+
// * No other preconditions for calling this function
24+
unsafe {
25+
let allocator = rcutils_get_default_allocator();
26+
let _lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
27+
rcl_logging_configure(args, &allocator).ok()?;
28+
}
29+
Ok(Self)
30+
}
31+
32+
/// SAFETY: Ensure rcl_context_t is valid before passing it in.
33+
pub(crate) unsafe fn configure(context: &rcl_context_t) -> Result<Arc<LoggingLifecycle>, RclrsError> {
34+
static CONFIGURATION: LazyLock<LoggingConfiguration> = LazyLock::new(
35+
|| LoggingConfiguration { lifecycle: Mutex::new(Weak::new())}
36+
);
37+
38+
let mut lifecycle = CONFIGURATION.lifecycle.lock().unwrap();
39+
if let Some(arc_lifecycle) = lifecycle.upgrade() {
40+
return Ok(arc_lifecycle);
41+
}
42+
let arc_lifecycle = Arc::new(LoggingLifecycle::new(&context.global_arguments)?);
43+
*lifecycle = Arc::downgrade(&arc_lifecycle);
44+
Ok(arc_lifecycle)
45+
}
46+
}
47+
48+
impl Drop for LoggingLifecycle {
49+
fn drop(&mut self) {
50+
let _lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
51+
unsafe { rcl_logging_fini(); }
52+
}
53+
}

rclrs/src/node.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,15 +557,15 @@ mod tests {
557557
#[test]
558558
fn test_logger_name() -> Result<(), RclrsError> {
559559
// Use helper to create 2 nodes for us
560-
let graph = construct_test_graph("test_topics_graph")?;
560+
let graph = construct_test_graph("test_logger_name")?;
561561

562562
assert_eq!(
563563
graph.node1.logger_name(),
564-
"test_topics_graph.graph_test_node_1"
564+
"test_logger_name.graph_test_node_1"
565565
);
566566
assert_eq!(
567567
graph.node2.logger_name(),
568-
"test_topics_graph.graph_test_node_2"
568+
"test_logger_name.graph_test_node_2"
569569
);
570570

571571
Ok(())

rclrs/src/node/graph.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,12 +487,13 @@ mod tests {
487487
.unwrap();
488488
let node_name = "test_publisher_names_and_types";
489489
let node = Node::new(&context, node_name).unwrap();
490-
// Test that the graph has no publishers
490+
// Test that the graph has no publishers besides /rosout
491491
let names_and_topics = node
492492
.get_publisher_names_and_types_by_node(node_name, "")
493493
.unwrap();
494494

495-
assert_eq!(names_and_topics.len(), 0);
495+
assert_eq!(names_and_topics.len(), 1);
496+
assert_eq!(names_and_topics.get("/rosout").unwrap().first().unwrap(), "rcl_interfaces/msg/Log");
496497

497498
let num_publishers = node.count_publishers("/test").unwrap();
498499

@@ -535,10 +536,11 @@ mod tests {
535536

536537
assert_eq!(names_and_topics.len(), 0);
537538

538-
// Test that the graph has no topics
539+
// Test that the graph has no topics besides /rosout
539540
let names_and_topics = node.get_topic_names_and_types().unwrap();
540541

541-
assert_eq!(names_and_topics.len(), 0);
542+
assert_eq!(names_and_topics.len(), 1);
543+
assert_eq!(names_and_topics.get("/rosout").unwrap().first().unwrap(), "rcl_interfaces/msg/Log");
542544
}
543545

544546
#[test]

rclrs/src/parameter.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ mod tests {
885885
String::from("declared_int:=10"),
886886
])
887887
.unwrap();
888-
let node = create_node(&ctx, "param_test_node").unwrap();
888+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
889889

890890
// Declaring a parameter with a different type than what was overridden should return an
891891
// error
@@ -943,7 +943,7 @@ mod tests {
943943
String::from("non_declared_string:='param'"),
944944
])
945945
.unwrap();
946-
let node = create_node(&ctx, "param_test_node").unwrap();
946+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
947947

948948
let overridden_int = node
949949
.declare_parameter("declared_int")
@@ -1093,7 +1093,7 @@ mod tests {
10931093
String::from("declared_int:=10"),
10941094
])
10951095
.unwrap();
1096-
let node = create_node(&ctx, "param_test_node").unwrap();
1096+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
10971097
// If a parameter was set as an override and as an undeclared parameter, the undeclared
10981098
// value should get priority
10991099
node.use_undeclared_parameters()
@@ -1115,7 +1115,7 @@ mod tests {
11151115
String::from("declared_int:=10"),
11161116
])
11171117
.unwrap();
1118-
let node = create_node(&ctx, "param_test_node").unwrap();
1118+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
11191119
{
11201120
// Setting a parameter with an override
11211121
let param = node
@@ -1161,7 +1161,7 @@ mod tests {
11611161
#[test]
11621162
fn test_parameter_ranges() {
11631163
let ctx = Context::new([]).unwrap();
1164-
let node = create_node(&ctx, "param_test_node").unwrap();
1164+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
11651165
// Setting invalid ranges should fail
11661166
let range = ParameterRange {
11671167
lower: Some(10),
@@ -1289,7 +1289,7 @@ mod tests {
12891289
#[test]
12901290
fn test_readonly_parameters() {
12911291
let ctx = Context::new([]).unwrap();
1292-
let node = create_node(&ctx, "param_test_node").unwrap();
1292+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
12931293
let param = node
12941294
.declare_parameter("int_param")
12951295
.default(100)
@@ -1316,7 +1316,7 @@ mod tests {
13161316
#[test]
13171317
fn test_preexisting_value_error() {
13181318
let ctx = Context::new([]).unwrap();
1319-
let node = create_node(&ctx, "param_test_node").unwrap();
1319+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
13201320
node.use_undeclared_parameters()
13211321
.set("int_param", 100)
13221322
.unwrap();
@@ -1369,7 +1369,7 @@ mod tests {
13691369
#[test]
13701370
fn test_optional_parameter_apis() {
13711371
let ctx = Context::new([]).unwrap();
1372-
let node = create_node(&ctx, "param_test_node").unwrap();
1372+
let node = create_node(&ctx, &format!("param_test_node_{}", line!())).unwrap();
13731373
node.declare_parameter::<i64>("int_param")
13741374
.optional()
13751375
.unwrap();

rclrs/src/subscription.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ mod tests {
381381

382382
// Test get_subscriptions_info_by_topic()
383383
let expected_subscriptions_info = vec![TopicEndpointInfo {
384-
node_name: String::from("graph_test_node_2"),
384+
node_name: format!("graph_test_node_2"),
385385
node_namespace: String::from(namespace),
386386
topic_type: String::from("test_msgs/msg/Empty"),
387387
}];

rclrs/src/time_source.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ mod tests {
149149

150150
#[test]
151151
fn time_source_default_clock() {
152-
let node = create_node(&Context::new([]).unwrap(), "test_node").unwrap();
152+
let node = create_node(&Context::new([]).unwrap(), &format!("time_source_test_node_{}", line!())).unwrap();
153153
// Default clock should be above 0 (use_sim_time is default false)
154154
assert!(node.get_clock().now().nsec > 0);
155155
}
@@ -162,7 +162,7 @@ mod tests {
162162
String::from("use_sim_time:=true"),
163163
])
164164
.unwrap();
165-
let node = create_node(&ctx, "test_node").unwrap();
165+
let node = create_node(&ctx, &format!("time_source_test_node_{}", line!())).unwrap();
166166
// Default sim time value should be 0 (no message received)
167167
assert_eq!(node.get_clock().now().nsec, 0);
168168
}

0 commit comments

Comments
 (0)