Skip to content

Commit c54a6f1

Browse files
authored
Cleanup time source object lifetimes (#1867)
* Small cleanups in TimeSource. Simplify some code, and also make sure to throw an exception when use_sim_time is not of type bool. Also add a test for the latter. * Remove the sim_time_cb_handler. It was originally used to make sure that someone didn't change the 'use_sim_time' type from boolean to something else. But since the parameters interface now does that automatically for us, we don't need the explicit check here. I can think of one situation that this allows that wasn't allowed before. If the user defined 'use_sim_time' as a parameter override when constructing a node, and the type is bool, and they also defined the parameters as 'dynamic_typing', then this callback will still have effect. But presumably if the user went out of their way to change the parameter to dynamic_typing, they are trying to do something esoteric and so we should just let them. * ClocksState does not need to be a pointer. Instead, make it a regular member variable. That lets us get rid of all of the special handling for when it is a weak_ptr or not. It's lifetime is now just that of NodeState. * Stop using NodeState as a weak_ptr in the captures. This ended up causing the lifetime of the object to be weird. Instead, just capture 'this' which is sufficient. * Make sure destroy_clock_sub is first. * Switch to using just a regular object. Windows objects to trying to do "make_shared" in the RCLCPP macro, so just switch to a normal object here. Signed-off-by: Chris Lalancette <[email protected]>
1 parent bca3fd7 commit c54a6f1

File tree

3 files changed

+70
-100
lines changed

3 files changed

+70
-100
lines changed

rclcpp/include/rclcpp/time_source.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,6 @@ class TimeSource
147147
~TimeSource();
148148

149149
private:
150-
class ClocksState;
151-
std::shared_ptr<ClocksState> clocks_state_;
152-
153150
class NodeState;
154151
std::shared_ptr<NodeState> node_state_;
155152

rclcpp/src/rclcpp/time_source.cpp

Lines changed: 51 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
namespace rclcpp
3434
{
3535

36-
class TimeSource::ClocksState : public std::enable_shared_from_this<ClocksState>
36+
class ClocksState final
3737
{
3838
public:
3939
ClocksState()
@@ -182,12 +182,11 @@ class TimeSource::ClocksState : public std::enable_shared_from_this<ClocksState>
182182
std::shared_ptr<const rosgraph_msgs::msg::Clock> last_msg_set_;
183183
};
184184

185-
class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
185+
class TimeSource::NodeState final
186186
{
187187
public:
188-
NodeState(std::weak_ptr<ClocksState> clocks_state, const rclcpp::QoS & qos, bool use_clock_thread)
189-
: clocks_state_(std::move(clocks_state)),
190-
use_clock_thread_(use_clock_thread),
188+
NodeState(const rclcpp::QoS & qos, bool use_clock_thread)
189+
: use_clock_thread_(use_clock_thread),
191190
logger_(rclcpp::get_logger("rclcpp")),
192191
qos_(qos)
193192
{
@@ -250,94 +249,77 @@ class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
250249
if (!node_parameters_->has_parameter(use_sim_time_name)) {
251250
use_sim_time_param = node_parameters_->declare_parameter(
252251
use_sim_time_name,
253-
rclcpp::ParameterValue(false),
254-
rcl_interfaces::msg::ParameterDescriptor());
252+
rclcpp::ParameterValue(false));
255253
} else {
256254
use_sim_time_param = node_parameters_->get_parameter(use_sim_time_name).get_parameter_value();
257255
}
258256
if (use_sim_time_param.get_type() == rclcpp::PARAMETER_BOOL) {
259257
if (use_sim_time_param.get<bool>()) {
260258
parameter_state_ = SET_TRUE;
261-
// There should be no way to call this attachNode when the clocks_state_ pointer is not
262-
// valid because it means the TimeSource is being destroyed
263-
if (auto clocks_state_ptr = clocks_state_.lock()) {
264-
clocks_state_ptr->enable_ros_time();
265-
create_clock_sub();
266-
}
259+
clocks_state_.enable_ros_time();
260+
create_clock_sub();
267261
}
268262
} else {
269263
RCLCPP_ERROR(
270264
logger_, "Invalid type '%s' for parameter 'use_sim_time', should be 'bool'",
271265
rclcpp::to_string(use_sim_time_param.get_type()).c_str());
266+
throw std::invalid_argument("Invalid type for parameter 'use_sim_time', should be 'bool'");
272267
}
273-
sim_time_cb_handler_ = node_parameters_->add_on_set_parameters_callback(
274-
[use_sim_time_name](const std::vector<rclcpp::Parameter> & parameters) {
275-
rcl_interfaces::msg::SetParametersResult result;
276-
result.successful = true;
277-
for (const auto & parameter : parameters) {
278-
if (
279-
parameter.get_name() == use_sim_time_name &&
280-
parameter.get_type() != rclcpp::PARAMETER_BOOL)
281-
{
282-
result.successful = false;
283-
result.reason = "'" + use_sim_time_name + "' must be a bool";
284-
break;
285-
}
286-
}
287-
return result;
288-
});
289268

290269
// TODO(tfoote) use parameters interface not subscribe to events via topic ticketed #609
291270
parameter_subscription_ = rclcpp::AsyncParametersClient::on_parameter_event(
292271
node_topics_,
293-
[state = std::weak_ptr<NodeState>(this->shared_from_this())](
294-
std::shared_ptr<const rcl_interfaces::msg::ParameterEvent> event) {
295-
if (auto state_ptr = state.lock()) {
296-
state_ptr->on_parameter_event(event);
272+
[this](std::shared_ptr<const rcl_interfaces::msg::ParameterEvent> event) {
273+
if (node_base_ != nullptr) {
274+
this->on_parameter_event(event);
297275
}
298-
// Do nothing if the pointer could not be locked because it means the TimeSource is now
276+
// Do nothing if node_base_ is nullptr because it means the TimeSource is now
299277
// without an attached node
300278
});
301279
}
302280

303281
// Detach the attached node
304282
void detachNode()
305283
{
306-
// There should be no way to call detachNode when the clocks_state_ pointer is not valid
307-
// because it means the TimeSource is being destroyed
308-
if (auto clocks_state_ptr = clocks_state_.lock()) {
309-
clocks_state_ptr->disable_ros_time();
310-
}
284+
// destroy_clock_sub() *must* be first here, to ensure that the executor
285+
// can't possibly call any of the callbacks as we are cleaning up.
311286
destroy_clock_sub();
287+
clocks_state_.disable_ros_time();
312288
parameter_subscription_.reset();
313289
node_base_.reset();
314290
node_topics_.reset();
315291
node_graph_.reset();
316292
node_services_.reset();
317293
node_logging_.reset();
318294
node_clock_.reset();
319-
if (sim_time_cb_handler_ && node_parameters_) {
320-
node_parameters_->remove_on_set_parameters_callback(sim_time_cb_handler_.get());
321-
}
322-
sim_time_cb_handler_.reset();
323295
node_parameters_.reset();
324296
}
325297

298+
void attachClock(std::shared_ptr<rclcpp::Clock> clock)
299+
{
300+
clocks_state_.attachClock(std::move(clock));
301+
}
302+
303+
void detachClock(std::shared_ptr<rclcpp::Clock> clock)
304+
{
305+
clocks_state_.detachClock(std::move(clock));
306+
}
307+
326308
private:
327-
std::weak_ptr<ClocksState> clocks_state_;
309+
ClocksState clocks_state_;
328310

329311
// Dedicated thread for clock subscription.
330312
bool use_clock_thread_;
331313
std::thread clock_executor_thread_;
332314

333315
// Preserve the node reference
334-
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_;
335-
rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_;
336-
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_;
337-
rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_;
338-
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_;
339-
rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_;
340-
rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_;
316+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_{nullptr};
317+
rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_{nullptr};
318+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_{nullptr};
319+
rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_{nullptr};
320+
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_{nullptr};
321+
rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_{nullptr};
322+
rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_{nullptr};
341323

342324
// Store (and update on node attach) logger for logging.
343325
Logger logger_;
@@ -346,9 +328,7 @@ class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
346328
rclcpp::QoS qos_;
347329

348330
// The subscription for the clock callback
349-
using MessageT = rosgraph_msgs::msg::Clock;
350-
using Alloc = std::allocator<void>;
351-
using SubscriptionT = rclcpp::Subscription<MessageT, Alloc>;
331+
using SubscriptionT = rclcpp::Subscription<rosgraph_msgs::msg::Clock>;
352332
std::shared_ptr<SubscriptionT> clock_subscription_{nullptr};
353333
std::mutex clock_sub_lock_;
354334
rclcpp::CallbackGroup::SharedPtr clock_callback_group_;
@@ -358,21 +338,15 @@ class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
358338
// The clock callback itself
359339
void clock_cb(std::shared_ptr<const rosgraph_msgs::msg::Clock> msg)
360340
{
361-
auto clocks_state_ptr = clocks_state_.lock();
362-
if (!clocks_state_ptr) {
363-
// The clock_state_ pointer is no longer valid, implying that the TimeSource object is being
364-
// destroyed, so do nothing
365-
return;
366-
}
367-
if (!clocks_state_ptr->is_ros_time_active() && SET_TRUE == this->parameter_state_) {
368-
clocks_state_ptr->enable_ros_time();
341+
if (!clocks_state_.is_ros_time_active() && SET_TRUE == this->parameter_state_) {
342+
clocks_state_.enable_ros_time();
369343
}
370344
// Cache the last message in case a new clock is attached.
371-
clocks_state_ptr->cache_last_msg(msg);
345+
clocks_state_.cache_last_msg(msg);
372346
auto time_msg = std::make_shared<builtin_interfaces::msg::Time>(msg->clock);
373347

374348
if (SET_TRUE == this->parameter_state_) {
375-
clocks_state_ptr->set_all_clocks(time_msg, true);
349+
clocks_state_.set_all_clocks(time_msg, true);
376350
}
377351
}
378352

@@ -421,13 +395,12 @@ class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
421395
node_topics_,
422396
"/clock",
423397
qos_,
424-
[state = std::weak_ptr<NodeState>(this->shared_from_this())](
425-
std::shared_ptr<const rosgraph_msgs::msg::Clock> msg) {
426-
if (auto state_ptr = state.lock()) {
427-
state_ptr->clock_cb(msg);
398+
[this](std::shared_ptr<const rosgraph_msgs::msg::Clock> msg) {
399+
// We are using node_base_ as an indication if there is a node attached.
400+
// Only call the clock_cb if that is the case.
401+
if (node_base_ != nullptr) {
402+
clock_cb(msg);
428403
}
429-
// Do nothing if the pointer could not be locked because it means the TimeSource is now
430-
// without an attached node
431404
},
432405
options
433406
);
@@ -447,19 +420,12 @@ class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
447420
}
448421

449422
// Parameter Event subscription
450-
using ParamMessageT = rcl_interfaces::msg::ParameterEvent;
451-
using ParamSubscriptionT = rclcpp::Subscription<ParamMessageT, Alloc>;
423+
using ParamSubscriptionT = rclcpp::Subscription<rcl_interfaces::msg::ParameterEvent>;
452424
std::shared_ptr<ParamSubscriptionT> parameter_subscription_;
453425

454426
// Callback for parameter updates
455427
void on_parameter_event(std::shared_ptr<const rcl_interfaces::msg::ParameterEvent> event)
456428
{
457-
auto clocks_state_ptr = clocks_state_.lock();
458-
if (!clocks_state_ptr) {
459-
// The clock_state_ pointer is no longer valid, implying that the TimeSource object is being
460-
// destroyed, so do nothing
461-
return;
462-
}
463429
// Filter out events on 'use_sim_time' parameter instances in other nodes.
464430
if (event->node != node_base_->get_fully_qualified_name()) {
465431
return;
@@ -475,41 +441,35 @@ class TimeSource::NodeState : public std::enable_shared_from_this<NodeState>
475441
}
476442
if (it.second->value.bool_value) {
477443
parameter_state_ = SET_TRUE;
478-
clocks_state_ptr->enable_ros_time();
444+
clocks_state_.enable_ros_time();
479445
create_clock_sub();
480446
} else {
481447
parameter_state_ = SET_FALSE;
482-
clocks_state_ptr->disable_ros_time();
483448
destroy_clock_sub();
449+
clocks_state_.disable_ros_time();
484450
}
485451
}
486452
// Handle the case that use_sim_time was deleted.
487453
rclcpp::ParameterEventsFilter deleted(event, {"use_sim_time"},
488454
{rclcpp::ParameterEventsFilter::EventType::DELETED});
489455
for (auto & it : deleted.get_events()) {
490456
(void) it; // if there is a match it's already matched, don't bother reading it.
491-
// If the parameter is deleted mark it as unset but dont' change state.
457+
// If the parameter is deleted mark it as unset but don't change state.
492458
parameter_state_ = UNSET;
493459
}
494460
}
495461

496462
// An enum to hold the parameter state
497463
enum UseSimTimeParameterState {UNSET, SET_TRUE, SET_FALSE};
498464
UseSimTimeParameterState parameter_state_;
499-
500-
// A handler for the use_sim_time parameter callback.
501-
rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr sim_time_cb_handler_{nullptr};
502465
};
503466

504467
TimeSource::TimeSource(
505468
std::shared_ptr<rclcpp::Node> node,
506469
const rclcpp::QoS & qos,
507470
bool use_clock_thread)
508-
: constructed_use_clock_thread_(use_clock_thread),
509-
constructed_qos_(qos)
471+
: TimeSource(qos, use_clock_thread)
510472
{
511-
clocks_state_ = std::make_shared<ClocksState>();
512-
node_state_ = std::make_shared<NodeState>(clocks_state_->weak_from_this(), qos, use_clock_thread);
513473
attachNode(node);
514474
}
515475

@@ -519,8 +479,7 @@ TimeSource::TimeSource(
519479
: constructed_use_clock_thread_(use_clock_thread),
520480
constructed_qos_(qos)
521481
{
522-
clocks_state_ = std::make_shared<ClocksState>();
523-
node_state_ = std::make_shared<NodeState>(clocks_state_->weak_from_this(), qos, use_clock_thread);
482+
node_state_ = std::make_shared<NodeState>(qos, use_clock_thread);
524483
}
525484

526485
void TimeSource::attachNode(rclcpp::Node::SharedPtr node)
@@ -559,19 +518,18 @@ void TimeSource::detachNode()
559518
{
560519
node_state_.reset();
561520
node_state_ = std::make_shared<NodeState>(
562-
clocks_state_->weak_from_this(),
563521
constructed_qos_,
564522
constructed_use_clock_thread_);
565523
}
566524

567525
void TimeSource::attachClock(std::shared_ptr<rclcpp::Clock> clock)
568526
{
569-
clocks_state_->attachClock(std::move(clock));
527+
node_state_->attachClock(std::move(clock));
570528
}
571529

572530
void TimeSource::detachClock(std::shared_ptr<rclcpp::Clock> clock)
573531
{
574-
clocks_state_->detachClock(std::move(clock));
532+
node_state_->detachClock(std::move(clock));
575533
}
576534

577535
bool TimeSource::get_use_clock_thread()

rclcpp/test/rclcpp/test_time_source.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include "rclcpp/time.hpp"
2828
#include "rclcpp/time_source.hpp"
2929

30+
#include "../utils/rclcpp_gtest_macros.hpp"
31+
3032
using namespace std::chrono_literals;
3133

3234
class TestTimeSource : public ::testing::Test
@@ -246,11 +248,25 @@ TEST_F(TestTimeSource, ROS_time_valid_sim_time) {
246248
}
247249

248250
TEST_F(TestTimeSource, ROS_invalid_sim_time) {
249-
rclcpp::TimeSource ts;
250-
ts.attachNode(node);
251+
rclcpp::TimeSource ts(node);
251252
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter("use_sim_time", "not boolean")).successful);
252253
}
253254

255+
TEST(TimeSource, invalid_sim_time_parameter_override)
256+
{
257+
rclcpp::init(0, nullptr);
258+
259+
rclcpp::NodeOptions options;
260+
options.automatically_declare_parameters_from_overrides(true);
261+
options.append_parameter_override("use_sim_time", "not boolean");
262+
263+
RCLCPP_EXPECT_THROW_EQ(
264+
rclcpp::Node("my_node", options),
265+
std::invalid_argument("Invalid type for parameter 'use_sim_time', should be 'bool'"));
266+
267+
rclcpp::shutdown();
268+
}
269+
254270
TEST_F(TestTimeSource, clock) {
255271
rclcpp::TimeSource ts(node);
256272
auto ros_clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
@@ -749,8 +765,7 @@ TEST_F(TestTimeSource, clock_sleep_until_with_ros_time_basic) {
749765

750766
node->set_parameter({"use_sim_time", true});
751767
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
752-
rclcpp::TimeSource time_source;
753-
time_source.attachNode(node);
768+
rclcpp::TimeSource time_source(node);
754769
time_source.attachClock(clock);
755770

756771
// Wait until time source has definitely received a first ROS time from the pub node

0 commit comments

Comments
 (0)