Skip to content

Commit e5d13a2

Browse files
Bugfix 20210810 get current state (#1756)
* protect state_machine_ with mutex lock. protect state_handle_ with mutex lock. reconsider mutex lock scope. remove mutex lock from constructors. lock just once during initialization of LifecycleNodeInterfaceImpl. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * Move updating of current_state to right after initialization. This is slightly more correct in the case that registering one of the services fails. Signed-off-by: Chris Lalancette <clalancette@openrobotics.org> Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> Signed-off-by: Chris Lalancette <clalancette@openrobotics.org> Co-authored-by: Chris Lalancette <clalancette@openrobotics.org>
1 parent d119157 commit e5d13a2

4 files changed

Lines changed: 95 additions & 53 deletions

File tree

rclcpp_lifecycle/include/rclcpp_lifecycle/state.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef RCLCPP_LIFECYCLE__STATE_HPP_
1616
#define RCLCPP_LIFECYCLE__STATE_HPP_
1717

18+
#include <mutex>
1819
#include <string>
1920

2021
#include "rcl_lifecycle/data_types.h"
@@ -92,6 +93,7 @@ class State
9293

9394
bool owns_rcl_state_handle_;
9495

96+
mutable std::recursive_mutex state_handle_mutex_;
9597
rcl_lifecycle_state_t * state_handle_;
9698
};
9799

rclcpp_lifecycle/src/lifecycle_node_interface_impl.cpp

Lines changed: 86 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include <functional>
1616
#include <memory>
17+
#include <mutex>
1718
#include <stdexcept>
1819
#include <string>
1920
#include <utility>
@@ -58,7 +59,11 @@ LifecycleNode::LifecycleNodeInterfaceImpl::LifecycleNodeInterfaceImpl(
5859
LifecycleNode::LifecycleNodeInterfaceImpl::~LifecycleNodeInterfaceImpl()
5960
{
6061
rcl_node_t * node_handle = node_base_interface_->get_rcl_node_handle();
61-
auto ret = rcl_lifecycle_state_machine_fini(&state_machine_, node_handle);
62+
rcl_ret_t ret;
63+
{
64+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
65+
ret = rcl_lifecycle_state_machine_fini(&state_machine_, node_handle);
66+
}
6267
if (ret != RCL_RET_OK) {
6368
RCUTILS_LOG_FATAL_NAMED(
6469
"rclcpp_lifecycle",
@@ -72,7 +77,6 @@ LifecycleNode::LifecycleNodeInterfaceImpl::init(bool enable_communication_interf
7277
rcl_node_t * node_handle = node_base_interface_->get_rcl_node_handle();
7378
const rcl_node_options_t * node_options =
7479
rcl_node_get_options(node_base_interface_->get_rcl_node_handle());
75-
state_machine_ = rcl_lifecycle_get_zero_initialized_state_machine();
7680
auto state_machine_options = rcl_lifecycle_get_default_state_machine_options();
7781
state_machine_options.enable_com_interface = enable_communication_interface;
7882
state_machine_options.allocator = node_options->allocator;
@@ -83,6 +87,8 @@ LifecycleNode::LifecycleNodeInterfaceImpl::init(bool enable_communication_interf
8387
// The publisher takes a C-Typesupport since the publishing (i.e. creating
8488
// the message) is done fully in RCL.
8589
// Services are handled in C++, so that it needs a C++ typesupport structure.
90+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
91+
state_machine_ = rcl_lifecycle_get_zero_initialized_state_machine();
8692
rcl_ret_t ret = rcl_lifecycle_state_machine_init(
8793
&state_machine_,
8894
node_handle,
@@ -99,6 +105,8 @@ LifecycleNode::LifecycleNodeInterfaceImpl::init(bool enable_communication_interf
99105
node_base_interface_->get_name());
100106
}
101107

108+
current_state_ = State(state_machine_.current_state);
109+
102110
if (enable_communication_interface) {
103111
{ // change_state
104112
auto cb = std::bind(
@@ -182,8 +190,6 @@ LifecycleNode::LifecycleNodeInterfaceImpl::init(bool enable_communication_interf
182190
nullptr);
183191
}
184192
}
185-
186-
current_state_ = State(state_machine_.current_state);
187193
}
188194

189195
bool
@@ -202,28 +208,31 @@ LifecycleNode::LifecycleNodeInterfaceImpl::on_change_state(
202208
std::shared_ptr<ChangeStateSrv::Response> resp)
203209
{
204210
(void)header;
205-
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
206-
throw std::runtime_error(
207-
"Can't get state. State machine is not initialized.");
208-
}
211+
std::uint8_t transition_id;
212+
{
213+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
214+
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
215+
throw std::runtime_error("Can't get state. State machine is not initialized.");
216+
}
209217

210-
auto transition_id = req->transition.id;
211-
// if there's a label attached to the request,
212-
// we check the transition attached to this label.
213-
// we further can't compare the id of the looked up transition
214-
// because ros2 service call defaults all intergers to zero.
215-
// that means if we call ros2 service call ... {transition: {label: shutdown}}
216-
// the id of the request is 0 (zero) whereas the id from the lookup up transition
217-
// can be different.
218-
// the result of this is that the label takes presedence of the id.
219-
if (req->transition.label.size() != 0) {
220-
auto rcl_transition = rcl_lifecycle_get_transition_by_label(
221-
state_machine_.current_state, req->transition.label.c_str());
222-
if (rcl_transition == nullptr) {
223-
resp->success = false;
224-
return;
218+
transition_id = req->transition.id;
219+
// if there's a label attached to the request,
220+
// we check the transition attached to this label.
221+
// we further can't compare the id of the looked up transition
222+
// because ros2 service call defaults all intergers to zero.
223+
// that means if we call ros2 service call ... {transition: {label: shutdown}}
224+
// the id of the request is 0 (zero) whereas the id from the lookup up transition
225+
// can be different.
226+
// the result of this is that the label takes presedence of the id.
227+
if (req->transition.label.size() != 0) {
228+
auto rcl_transition = rcl_lifecycle_get_transition_by_label(
229+
state_machine_.current_state, req->transition.label.c_str());
230+
if (rcl_transition == nullptr) {
231+
resp->success = false;
232+
return;
233+
}
234+
transition_id = static_cast<std::uint8_t>(rcl_transition->id);
225235
}
226-
transition_id = static_cast<std::uint8_t>(rcl_transition->id);
227236
}
228237

229238
node_interfaces::LifecycleNodeInterface::CallbackReturn cb_return_code;
@@ -244,6 +253,7 @@ LifecycleNode::LifecycleNodeInterfaceImpl::on_get_state(
244253
{
245254
(void)header;
246255
(void)req;
256+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
247257
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
248258
throw std::runtime_error(
249259
"Can't get state. State machine is not initialized.");
@@ -260,6 +270,7 @@ LifecycleNode::LifecycleNodeInterfaceImpl::on_get_available_states(
260270
{
261271
(void)header;
262272
(void)req;
273+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
263274
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
264275
throw std::runtime_error(
265276
"Can't get available states. State machine is not initialized.");
@@ -282,6 +293,7 @@ LifecycleNode::LifecycleNodeInterfaceImpl::on_get_available_transitions(
282293
{
283294
(void)header;
284295
(void)req;
296+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
285297
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
286298
throw std::runtime_error(
287299
"Can't get available transitions. State machine is not initialized.");
@@ -309,6 +321,7 @@ LifecycleNode::LifecycleNodeInterfaceImpl::on_get_transition_graph(
309321
{
310322
(void)header;
311323
(void)req;
324+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
312325
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
313326
throw std::runtime_error(
314327
"Can't get available transitions. State machine is not initialized.");
@@ -338,6 +351,7 @@ std::vector<State>
338351
LifecycleNode::LifecycleNodeInterfaceImpl::get_available_states() const
339352
{
340353
std::vector<State> states;
354+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
341355
states.reserve(state_machine_.transition_map.states_size);
342356

343357
for (unsigned int i = 0; i < state_machine_.transition_map.states_size; ++i) {
@@ -350,6 +364,7 @@ std::vector<Transition>
350364
LifecycleNode::LifecycleNodeInterfaceImpl::get_available_transitions() const
351365
{
352366
std::vector<Transition> transitions;
367+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
353368
transitions.reserve(state_machine_.current_state->valid_transition_size);
354369

355370
for (unsigned int i = 0; i < state_machine_.current_state->valid_transition_size; ++i) {
@@ -362,6 +377,7 @@ std::vector<Transition>
362377
LifecycleNode::LifecycleNodeInterfaceImpl::get_transition_graph() const
363378
{
364379
std::vector<Transition> transitions;
380+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
365381
transitions.reserve(state_machine_.transition_map.transitions_size);
366382

367383
for (unsigned int i = 0; i < state_machine_.transition_map.transitions_size; ++i) {
@@ -375,26 +391,33 @@ LifecycleNode::LifecycleNodeInterfaceImpl::change_state(
375391
std::uint8_t transition_id,
376392
node_interfaces::LifecycleNodeInterface::CallbackReturn & cb_return_code)
377393
{
378-
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
379-
RCUTILS_LOG_ERROR(
380-
"Unable to change state for state machine for %s: %s",
381-
node_base_interface_->get_name(), rcl_get_error_string().str);
382-
return RCL_RET_ERROR;
383-
}
384-
385394
constexpr bool publish_update = true;
386-
// keep the initial state to pass to a transition callback
387-
State initial_state(state_machine_.current_state);
395+
State initial_state;
396+
unsigned int current_state_id;
388397

389-
if (
390-
rcl_lifecycle_trigger_transition_by_id(
391-
&state_machine_, transition_id, publish_update) != RCL_RET_OK)
392398
{
393-
RCUTILS_LOG_ERROR(
394-
"Unable to start transition %u from current state %s: %s",
395-
transition_id, state_machine_.current_state->label, rcl_get_error_string().str);
396-
rcutils_reset_error();
397-
return RCL_RET_ERROR;
399+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
400+
if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) {
401+
RCUTILS_LOG_ERROR(
402+
"Unable to change state for state machine for %s: %s",
403+
node_base_interface_->get_name(), rcl_get_error_string().str);
404+
return RCL_RET_ERROR;
405+
}
406+
407+
// keep the initial state to pass to a transition callback
408+
initial_state = State(state_machine_.current_state);
409+
410+
if (
411+
rcl_lifecycle_trigger_transition_by_id(
412+
&state_machine_, transition_id, publish_update) != RCL_RET_OK)
413+
{
414+
RCUTILS_LOG_ERROR(
415+
"Unable to start transition %u from current state %s: %s",
416+
transition_id, state_machine_.current_state->label, rcl_get_error_string().str);
417+
rcutils_reset_error();
418+
return RCL_RET_ERROR;
419+
}
420+
current_state_id = state_machine_.current_state->id;
398421
}
399422

400423
// Update the internal current_state_
@@ -411,18 +434,22 @@ LifecycleNode::LifecycleNodeInterfaceImpl::change_state(
411434
return rcl_lifecycle_transition_error_label;
412435
};
413436

414-
cb_return_code = execute_callback(state_machine_.current_state->id, initial_state);
437+
cb_return_code = execute_callback(current_state_id, initial_state);
415438
auto transition_label = get_label_for_return_code(cb_return_code);
416439

417-
if (
418-
rcl_lifecycle_trigger_transition_by_label(
419-
&state_machine_, transition_label, publish_update) != RCL_RET_OK)
420440
{
421-
RCUTILS_LOG_ERROR(
422-
"Failed to finish transition %u. Current state is now: %s (%s)",
423-
transition_id, state_machine_.current_state->label, rcl_get_error_string().str);
424-
rcutils_reset_error();
425-
return RCL_RET_ERROR;
441+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
442+
if (
443+
rcl_lifecycle_trigger_transition_by_label(
444+
&state_machine_, transition_label, publish_update) != RCL_RET_OK)
445+
{
446+
RCUTILS_LOG_ERROR(
447+
"Failed to finish transition %u. Current state is now: %s (%s)",
448+
transition_id, state_machine_.current_state->label, rcl_get_error_string().str);
449+
rcutils_reset_error();
450+
return RCL_RET_ERROR;
451+
}
452+
current_state_id = state_machine_.current_state->id;
426453
}
427454

428455
// Update the internal current_state_
@@ -433,8 +460,9 @@ LifecycleNode::LifecycleNodeInterfaceImpl::change_state(
433460
if (cb_return_code == node_interfaces::LifecycleNodeInterface::CallbackReturn::ERROR) {
434461
RCUTILS_LOG_WARN("Error occurred while doing error handling.");
435462

436-
auto error_cb_code = execute_callback(state_machine_.current_state->id, initial_state);
463+
auto error_cb_code = execute_callback(current_state_id, initial_state);
437464
auto error_cb_label = get_label_for_return_code(error_cb_code);
465+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
438466
if (
439467
rcl_lifecycle_trigger_transition_by_label(
440468
&state_machine_, error_cb_label, publish_update) != RCL_RET_OK)
@@ -486,8 +514,13 @@ const State & LifecycleNode::LifecycleNodeInterfaceImpl::trigger_transition(
486514
const char * transition_label,
487515
node_interfaces::LifecycleNodeInterface::CallbackReturn & cb_return_code)
488516
{
489-
auto transition =
490-
rcl_lifecycle_get_transition_by_label(state_machine_.current_state, transition_label);
517+
const rcl_lifecycle_transition_t * transition;
518+
{
519+
std::lock_guard<std::recursive_mutex> lock(state_machine_mutex_);
520+
521+
transition =
522+
rcl_lifecycle_get_transition_by_label(state_machine_.current_state, transition_label);
523+
}
491524
if (transition) {
492525
change_state(static_cast<uint8_t>(transition->id), cb_return_code);
493526
}

rclcpp_lifecycle/src/lifecycle_node_interface_impl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class LifecycleNode::LifecycleNodeInterfaceImpl final
143143
node_interfaces::LifecycleNodeInterface::CallbackReturn
144144
execute_callback(unsigned int cb_id, const State & previous_state) const;
145145

146+
mutable std::recursive_mutex state_machine_mutex_;
146147
rcl_lifecycle_state_machine_t state_machine_;
147148
State current_state_;
148149
std::map<

rclcpp_lifecycle/src/state.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ State::State(
7171
if (!rcl_lifecycle_state_handle) {
7272
throw std::runtime_error("rcl_lifecycle_state_handle is null");
7373
}
74+
7475
state_handle_ = const_cast<rcl_lifecycle_state_t *>(rcl_lifecycle_state_handle);
7576
}
7677

@@ -94,6 +95,8 @@ State::operator=(const State & rhs)
9495
return *this;
9596
}
9697

98+
// hold the lock until state_handle_ is reconstructed
99+
std::lock_guard<std::recursive_mutex> lock(state_handle_mutex_);
97100
// reset all currently used resources
98101
reset();
99102

@@ -129,6 +132,7 @@ State::operator=(const State & rhs)
129132
uint8_t
130133
State::id() const
131134
{
135+
std::lock_guard<std::recursive_mutex> lock(state_handle_mutex_);
132136
if (!state_handle_) {
133137
throw std::runtime_error("Error in state! Internal state_handle is NULL.");
134138
}
@@ -138,6 +142,7 @@ State::id() const
138142
std::string
139143
State::label() const
140144
{
145+
std::lock_guard<std::recursive_mutex> lock(state_handle_mutex_);
141146
if (!state_handle_) {
142147
throw std::runtime_error("Error in state! Internal state_handle is NULL.");
143148
}
@@ -147,6 +152,7 @@ State::label() const
147152
void
148153
State::reset() noexcept
149154
{
155+
std::lock_guard<std::recursive_mutex> lock(state_handle_mutex_);
150156
if (!owns_rcl_state_handle_) {
151157
state_handle_ = nullptr;
152158
}

0 commit comments

Comments
 (0)