Skip to content

Commit 91fb1dd

Browse files
Janosch MachowinskiJanosch Machowinski
authored andcommitted
fix(rclcpp_action): Fixed race condition in Client
Some background information: is_ready and take_data are guaranteed to be called in sequence without interruption from another thread. while execute is running, another thread may also call is_ready. The problem was, that is_goal_response_ready, is_result_response_ready, is_cancel_response_ready, is_feedback_ready and is_status_ready were accessed and written from is_ready and execute. This commit fixed this by only using the mentioned variables in is_ready and take_data. execute now only accesses the given pointer and works on this. Signed-off-by: Janosch Machowinski <[email protected]>
1 parent 147238c commit 91fb1dd

File tree

2 files changed

+124
-71
lines changed

2 files changed

+124
-71
lines changed

rclcpp_action/src/client.cpp

Lines changed: 120 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,62 @@ class ClientBaseImpl
122122
std::default_random_engine, 8, unsigned int> random_bytes_generator;
123123
};
124124

125+
struct ClientBaseData
126+
{
127+
struct FeedbackReadyData
128+
{
129+
FeedbackReadyData(rcl_ret_t retIn, std::shared_ptr<void> msg)
130+
: ret(retIn), feedback_message(msg) {}
131+
rcl_ret_t ret;
132+
std::shared_ptr<void> feedback_message;
133+
};
134+
struct StatusReadyData
135+
{
136+
StatusReadyData(rcl_ret_t retIn, std::shared_ptr<void> msg)
137+
: ret(retIn), status_message(msg) {}
138+
rcl_ret_t ret;
139+
std::shared_ptr<void> status_message;
140+
};
141+
struct GoalResponseData
142+
{
143+
GoalResponseData(rcl_ret_t retIn, rmw_request_id_t header, std::shared_ptr<void> response)
144+
: ret(retIn), response_header(header), goal_response(response) {}
145+
rcl_ret_t ret;
146+
rmw_request_id_t response_header;
147+
std::shared_ptr<void> goal_response;
148+
};
149+
struct CancelResponseData
150+
{
151+
CancelResponseData(rcl_ret_t retIn, rmw_request_id_t header, std::shared_ptr<void> response)
152+
: ret(retIn), response_header(header), cancel_response(response) {}
153+
rcl_ret_t ret;
154+
rmw_request_id_t response_header;
155+
std::shared_ptr<void> cancel_response;
156+
};
157+
struct ResultResponseData
158+
{
159+
ResultResponseData(rcl_ret_t retIn, rmw_request_id_t header, std::shared_ptr<void> response)
160+
: ret(retIn), response_header(header), result_response(response) {}
161+
rcl_ret_t ret;
162+
rmw_request_id_t response_header;
163+
std::shared_ptr<void> result_response;
164+
};
165+
166+
std::variant<FeedbackReadyData, StatusReadyData, GoalResponseData, CancelResponseData,
167+
ResultResponseData> data;
168+
169+
explicit ClientBaseData(FeedbackReadyData && dataIn)
170+
: data(std::move(dataIn)) {}
171+
explicit ClientBaseData(StatusReadyData && dataIn)
172+
: data(std::move(dataIn)) {}
173+
explicit ClientBaseData(GoalResponseData && dataIn)
174+
: data(std::move(dataIn)) {}
175+
explicit ClientBaseData(CancelResponseData && dataIn)
176+
: data(std::move(dataIn)) {}
177+
explicit ClientBaseData(ResultResponseData && dataIn)
178+
: data(std::move(dataIn)) {}
179+
};
180+
125181
ClientBase::ClientBase(
126182
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
127183
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
@@ -551,43 +607,53 @@ std::shared_ptr<void>
551607
ClientBase::take_data()
552608
{
553609
if (pimpl_->is_feedback_ready) {
610+
pimpl_->is_feedback_ready = false;
554611
std::shared_ptr<void> feedback_message = this->create_feedback_message();
555612
rcl_ret_t ret = rcl_action_take_feedback(
556613
pimpl_->client_handle.get(), feedback_message.get());
557614
return std::static_pointer_cast<void>(
558-
std::make_shared<std::tuple<rcl_ret_t, std::shared_ptr<void>>>(
559-
ret, feedback_message));
615+
std::make_shared<ClientBaseData>(
616+
ClientBaseData::FeedbackReadyData(
617+
ret, feedback_message)));
560618
} else if (pimpl_->is_status_ready) {
619+
pimpl_->is_status_ready = false;
561620
std::shared_ptr<void> status_message = this->create_status_message();
562621
rcl_ret_t ret = rcl_action_take_status(
563622
pimpl_->client_handle.get(), status_message.get());
564623
return std::static_pointer_cast<void>(
565-
std::make_shared<std::tuple<rcl_ret_t, std::shared_ptr<void>>>(
566-
ret, status_message));
624+
std::make_shared<ClientBaseData>(
625+
ClientBaseData::StatusReadyData(
626+
ret, status_message)));
567627
} else if (pimpl_->is_goal_response_ready) {
628+
pimpl_->is_goal_response_ready = false;
568629
rmw_request_id_t response_header;
569630
std::shared_ptr<void> goal_response = this->create_goal_response();
570631
rcl_ret_t ret = rcl_action_take_goal_response(
571632
pimpl_->client_handle.get(), &response_header, goal_response.get());
572633
return std::static_pointer_cast<void>(
573-
std::make_shared<std::tuple<rcl_ret_t, rmw_request_id_t, std::shared_ptr<void>>>(
574-
ret, response_header, goal_response));
634+
std::make_shared<ClientBaseData>(
635+
ClientBaseData::GoalResponseData(
636+
ret, response_header, goal_response)));
575637
} else if (pimpl_->is_result_response_ready) {
638+
pimpl_->is_result_response_ready = false;
576639
rmw_request_id_t response_header;
577640
std::shared_ptr<void> result_response = this->create_result_response();
578641
rcl_ret_t ret = rcl_action_take_result_response(
579642
pimpl_->client_handle.get(), &response_header, result_response.get());
580643
return std::static_pointer_cast<void>(
581-
std::make_shared<std::tuple<rcl_ret_t, rmw_request_id_t, std::shared_ptr<void>>>(
582-
ret, response_header, result_response));
644+
std::make_shared<ClientBaseData>(
645+
ClientBaseData::ResultResponseData(
646+
ret, response_header, result_response)));
583647
} else if (pimpl_->is_cancel_response_ready) {
648+
pimpl_->is_cancel_response_ready = false;
584649
rmw_request_id_t response_header;
585650
std::shared_ptr<void> cancel_response = this->create_cancel_response();
586651
rcl_ret_t ret = rcl_action_take_cancel_response(
587652
pimpl_->client_handle.get(), &response_header, cancel_response.get());
588653
return std::static_pointer_cast<void>(
589-
std::make_shared<std::tuple<rcl_ret_t, rmw_request_id_t, std::shared_ptr<void>>>(
590-
ret, response_header, cancel_response));
654+
std::make_shared<ClientBaseData>(
655+
ClientBaseData::CancelResponseData(
656+
ret, response_header, cancel_response)));
591657
} else {
592658
throw std::runtime_error("Taking data from action client but nothing is ready");
593659
}
@@ -619,71 +685,54 @@ ClientBase::take_data_by_entity_id(size_t id)
619685
}
620686

621687
void
622-
ClientBase::execute(std::shared_ptr<void> & data)
688+
ClientBase::execute(std::shared_ptr<void> & dataIn)
623689
{
624-
if (!data) {
690+
if (!dataIn) {
625691
throw std::runtime_error("'data' is empty");
626692
}
627693

628-
if (pimpl_->is_feedback_ready) {
629-
auto shared_ptr = std::static_pointer_cast<std::tuple<rcl_ret_t, std::shared_ptr<void>>>(data);
630-
auto ret = std::get<0>(*shared_ptr);
631-
pimpl_->is_feedback_ready = false;
632-
if (RCL_RET_OK == ret) {
633-
auto feedback_message = std::get<1>(*shared_ptr);
634-
this->handle_feedback_message(feedback_message);
635-
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != ret) {
636-
rclcpp::exceptions::throw_from_rcl_error(ret, "error taking feedback");
637-
}
638-
} else if (pimpl_->is_status_ready) {
639-
auto shared_ptr = std::static_pointer_cast<std::tuple<rcl_ret_t, std::shared_ptr<void>>>(data);
640-
auto ret = std::get<0>(*shared_ptr);
641-
pimpl_->is_status_ready = false;
642-
if (RCL_RET_OK == ret) {
643-
auto status_message = std::get<1>(*shared_ptr);
644-
this->handle_status_message(status_message);
645-
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != ret) {
646-
rclcpp::exceptions::throw_from_rcl_error(ret, "error taking status");
647-
}
648-
} else if (pimpl_->is_goal_response_ready) {
649-
auto shared_ptr = std::static_pointer_cast<
650-
std::tuple<rcl_ret_t, rmw_request_id_t, std::shared_ptr<void>>>(data);
651-
auto ret = std::get<0>(*shared_ptr);
652-
pimpl_->is_goal_response_ready = false;
653-
if (RCL_RET_OK == ret) {
654-
auto response_header = std::get<1>(*shared_ptr);
655-
auto goal_response = std::get<2>(*shared_ptr);
656-
this->handle_goal_response(response_header, goal_response);
657-
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != ret) {
658-
rclcpp::exceptions::throw_from_rcl_error(ret, "error taking goal response");
659-
}
660-
} else if (pimpl_->is_result_response_ready) {
661-
auto shared_ptr = std::static_pointer_cast<
662-
std::tuple<rcl_ret_t, rmw_request_id_t, std::shared_ptr<void>>>(data);
663-
auto ret = std::get<0>(*shared_ptr);
664-
pimpl_->is_result_response_ready = false;
665-
if (RCL_RET_OK == ret) {
666-
auto response_header = std::get<1>(*shared_ptr);
667-
auto result_response = std::get<2>(*shared_ptr);
668-
this->handle_result_response(response_header, result_response);
669-
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != ret) {
670-
rclcpp::exceptions::throw_from_rcl_error(ret, "error taking result response");
671-
}
672-
} else if (pimpl_->is_cancel_response_ready) {
673-
auto shared_ptr = std::static_pointer_cast<
674-
std::tuple<rcl_ret_t, rmw_request_id_t, std::shared_ptr<void>>>(data);
675-
auto ret = std::get<0>(*shared_ptr);
676-
pimpl_->is_cancel_response_ready = false;
677-
if (RCL_RET_OK == ret) {
678-
auto response_header = std::get<1>(*shared_ptr);
679-
auto cancel_response = std::get<2>(*shared_ptr);
680-
this->handle_cancel_response(response_header, cancel_response);
681-
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != ret) {
682-
rclcpp::exceptions::throw_from_rcl_error(ret, "error taking cancel response");
683-
}
684-
} else {
685-
throw std::runtime_error("Executing action client but nothing is ready");
686-
}
694+
std::shared_ptr<ClientBaseData> dataPtr = std::static_pointer_cast<ClientBaseData>(dataIn);
695+
696+
697+
std::visit(
698+
[&](auto && data) -> void {
699+
using T = std::decay_t<decltype(data)>;
700+
if constexpr (std::is_same_v<T, ClientBaseData::FeedbackReadyData>) {
701+
if (RCL_RET_OK == data.ret) {
702+
this->handle_feedback_message(data.feedback_message);
703+
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != data.ret) {
704+
rclcpp::exceptions::throw_from_rcl_error(data.ret, "error taking feedback");
705+
}
706+
}
707+
if constexpr (std::is_same_v<T, ClientBaseData::StatusReadyData>) {
708+
if (RCL_RET_OK == data.ret) {
709+
this->handle_status_message(data.status_message);
710+
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != data.ret) {
711+
rclcpp::exceptions::throw_from_rcl_error(data.ret, "error taking status");
712+
}
713+
}
714+
if constexpr (std::is_same_v<T, ClientBaseData::GoalResponseData>) {
715+
if (RCL_RET_OK == data.ret) {
716+
this->handle_goal_response(data.response_header, data.goal_response);
717+
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != data.ret) {
718+
rclcpp::exceptions::throw_from_rcl_error(data.ret, "error taking goal response");
719+
}
720+
}
721+
if constexpr (std::is_same_v<T, ClientBaseData::ResultResponseData>) {
722+
if (RCL_RET_OK == data.ret) {
723+
this->handle_result_response(data.response_header, data.result_response);
724+
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != data.ret) {
725+
rclcpp::exceptions::throw_from_rcl_error(data.ret, "error taking result response");
726+
}
727+
}
728+
if constexpr (std::is_same_v<T, ClientBaseData::CancelResponseData>) {
729+
if (RCL_RET_OK == data.ret) {
730+
this->handle_cancel_response(data.response_header, data.cancel_response);
731+
} else if (RCL_RET_ACTION_CLIENT_TAKE_FAILED != data.ret) {
732+
rclcpp::exceptions::throw_from_rcl_error(data.ret, "error taking cancel response");
733+
}
734+
}
735+
}, dataPtr->data);
687736
}
688737

689738
} // namespace rclcpp_action

rclcpp_action/src/server.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ ServerBase::take_data_by_entity_id(size_t id)
347347
void
348348
ServerBase::execute(std::shared_ptr<void> & dataIn)
349349
{
350+
if (!dataIn) {
351+
throw std::runtime_error("ServerBase::execute: give data pointer was null");
352+
}
353+
350354
std::shared_ptr<ServerBaseData> dataPtr = std::static_pointer_cast<ServerBaseData>(dataIn);
351355

352356
std::visit(

0 commit comments

Comments
 (0)