Skip to content

Commit 24ee7de

Browse files
Janosch MachowinskiJanosch Machowinski
authored andcommitted
fix: Fixed race condition in action server between is_ready and take_data and execute
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 goal_request_ready_, cancel_request_ready_, result_request_ready_ and goal_expired_ 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 22a954e commit 24ee7de

File tree

2 files changed

+118
-68
lines changed

2 files changed

+118
-68
lines changed

rclcpp_action/include/rclcpp_action/server.hpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <unordered_map>
2323
#include <utility>
2424

25+
#include "action_msgs/srv/cancel_goal.hpp"
2526
#include "rcl/event_callback.h"
2627
#include "rcl_action/action_server.h"
2728
#include "rosidl_runtime_c/action_type_support_struct.h"
@@ -279,19 +280,25 @@ class ServerBase : public rclcpp::Waitable
279280
/// \internal
280281
RCLCPP_ACTION_PUBLIC
281282
void
282-
execute_goal_request_received(std::shared_ptr<void> & data);
283+
execute_goal_request_received(
284+
rcl_ret_t ret, rcl_action_goal_info_t goal_info, rmw_request_id_t request_header,
285+
std::shared_ptr<void> message);
283286

284287
/// Handle a request to cancel goals on the server
285288
/// \internal
286289
RCLCPP_ACTION_PUBLIC
287290
void
288-
execute_cancel_request_received(std::shared_ptr<void> & data);
291+
execute_cancel_request_received(
292+
rcl_ret_t ret, std::shared_ptr<action_msgs::srv::CancelGoal::Request> request,
293+
rmw_request_id_t request_header);
289294

290295
/// Handle a request to get the result of an action
291296
/// \internal
292297
RCLCPP_ACTION_PUBLIC
293298
void
294-
execute_result_request_received(std::shared_ptr<void> & data);
299+
execute_result_request_received(
300+
rcl_ret_t ret, std::shared_ptr<void> result_request,
301+
rmw_request_id_t request_header);
295302

296303
/// Handle a timeout indicating a completed goal should be forgotten by the server
297304
/// \internal

rclcpp_action/src/server.cpp

Lines changed: 108 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#include "rcpputils/scope_exit.hpp"
2727

2828
#include "action_msgs/msg/goal_status_array.hpp"
29-
#include "action_msgs/srv/cancel_goal.hpp"
3029
#include "rclcpp/exceptions.hpp"
3130
#include "rclcpp_action/server.hpp"
3231

@@ -65,6 +64,11 @@ class ServerBaseImpl
6564
std::atomic<bool> result_request_ready_{false};
6665
std::atomic<bool> goal_expired_{false};
6766

67+
// variable used to check, that the sequence of
68+
// is_ready and take_data is never interrupted by
69+
// another thread.
70+
std::atomic<bool> threadExlusive{false};
71+
6872
// Lock for unordered_maps
6973
std::recursive_mutex unordered_map_mutex_;
7074

@@ -78,6 +82,30 @@ class ServerBaseImpl
7882
rclcpp::Logger logger_;
7983
};
8084
} // namespace rclcpp_action
85+
struct ServerBaseData
86+
{
87+
using GoalRequestData = std::tuple<rcl_ret_t, const rcl_action_goal_info_t, rmw_request_id_t,
88+
std::shared_ptr<void>>;
89+
90+
using CancelRequestData = std::tuple<rcl_ret_t,
91+
std::shared_ptr<action_msgs::srv::CancelGoal::Request>,
92+
rmw_request_id_t>;
93+
94+
using ResultRequestData = std::tuple<rcl_ret_t, std::shared_ptr<void>, rmw_request_id_t>;
95+
96+
using GoalExpiredData = struct Empty {};
97+
98+
std::variant<GoalRequestData, CancelRequestData, ResultRequestData, GoalExpiredData> data;
99+
100+
explicit ServerBaseData(GoalRequestData && dataIn)
101+
: data(std::move(dataIn)) {}
102+
explicit ServerBaseData(CancelRequestData && dataIn)
103+
: data(std::move(dataIn)) {}
104+
explicit ServerBaseData(ResultRequestData && dataIn)
105+
: data(std::move(dataIn)) {}
106+
explicit ServerBaseData(GoalExpiredData && dataIn)
107+
: data(std::move(dataIn)) {}
108+
};
81109

82110
ServerBase::ServerBase(
83111
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
@@ -178,6 +206,11 @@ ServerBase::add_to_wait_set(rcl_wait_set_t * wait_set)
178206
bool
179207
ServerBase::is_ready(rcl_wait_set_t * wait_set)
180208
{
209+
if (pimpl_->threadExlusive.exchange(true)) {
210+
throw std::runtime_error(
211+
"ServerBase::Internal error, is_ready called, before take_data was called");
212+
}
213+
181214
bool goal_request_ready;
182215
bool cancel_request_ready;
183216
bool result_request_ready;
@@ -203,16 +236,31 @@ ServerBase::is_ready(rcl_wait_set_t * wait_set)
203236
rclcpp::exceptions::throw_from_rcl_error(ret);
204237
}
205238

206-
return pimpl_->goal_request_ready_.load() ||
207-
pimpl_->cancel_request_ready_.load() ||
208-
pimpl_->result_request_ready_.load() ||
209-
pimpl_->goal_expired_.load();
239+
240+
bool isReady = pimpl_->goal_request_ready_.load() ||
241+
pimpl_->cancel_request_ready_.load() ||
242+
pimpl_->result_request_ready_.load() ||
243+
pimpl_->goal_expired_.load();
244+
245+
if (!isReady) {
246+
if (!pimpl_->threadExlusive.exchange(false)) {
247+
throw std::runtime_error("ServerBase::Internal error, is_ready reentrant called");
248+
}
249+
}
250+
return isReady;
210251
}
211252

212253
std::shared_ptr<void>
213254
ServerBase::take_data()
214255
{
215-
if (pimpl_->goal_request_ready_.load()) {
256+
auto checkCallSequence = [this]() {
257+
if (!pimpl_->threadExlusive.exchange(false)) {
258+
throw std::runtime_error(
259+
"ServerBase::Internal error, take_data called, before is_ready was called");
260+
}
261+
};
262+
263+
if (pimpl_->goal_request_ready_.exchange(false)) {
216264
rcl_ret_t ret;
217265
rcl_action_goal_info_t goal_info = rcl_action_get_zero_initialized_goal_info();
218266
rmw_request_id_t request_header;
@@ -225,13 +273,12 @@ ServerBase::take_data()
225273
&request_header,
226274
message.get());
227275

276+
checkCallSequence();
228277
return std::static_pointer_cast<void>(
229-
std::make_shared
230-
<std::tuple<rcl_ret_t, rcl_action_goal_info_t, rmw_request_id_t, std::shared_ptr<void>>>(
231-
ret,
232-
goal_info,
233-
request_header, message));
234-
} else if (pimpl_->cancel_request_ready_.load()) {
278+
std::make_shared<ServerBaseData>(
279+
ServerBaseData::GoalRequestData(
280+
ret, goal_info, request_header, message)));
281+
} else if (pimpl_->cancel_request_ready_.exchange(false)) {
235282
rcl_ret_t ret;
236283
rmw_request_id_t request_header;
237284

@@ -244,11 +291,13 @@ ServerBase::take_data()
244291
&request_header,
245292
request.get());
246293

294+
checkCallSequence();
247295
return std::static_pointer_cast<void>(
248-
std::make_shared
249-
<std::tuple<rcl_ret_t, std::shared_ptr<action_msgs::srv::CancelGoal::Request>,
250-
rmw_request_id_t>>(ret, request, request_header));
251-
} else if (pimpl_->result_request_ready_.load()) {
296+
std::make_shared<ServerBaseData>(
297+
ServerBaseData::CancelRequestData(
298+
ret, request,
299+
request_header)));
300+
} else if (pimpl_->result_request_ready_.exchange(false)) {
252301
rcl_ret_t ret;
253302
// Get the result request message
254303
rmw_request_id_t request_header;
@@ -257,11 +306,15 @@ ServerBase::take_data()
257306
ret = rcl_action_take_result_request(
258307
pimpl_->action_server_.get(), &request_header, result_request.get());
259308

309+
checkCallSequence();
260310
return std::static_pointer_cast<void>(
261-
std::make_shared<std::tuple<rcl_ret_t, std::shared_ptr<void>, rmw_request_id_t>>(
262-
ret, result_request, request_header));
311+
std::make_shared<ServerBaseData>(
312+
ServerBaseData::ResultRequestData(
313+
ret, result_request, request_header)));
263314
} else if (pimpl_->goal_expired_.load()) {
264-
return nullptr;
315+
checkCallSequence();
316+
return std::static_pointer_cast<void>(
317+
std::make_shared<ServerBaseData>(ServerBaseData::GoalExpiredData()));
265318
} else {
266319
throw std::runtime_error("Taking data from action server but nothing is ready");
267320
}
@@ -270,6 +323,11 @@ ServerBase::take_data()
270323
std::shared_ptr<void>
271324
ServerBase::take_data_by_entity_id(size_t id)
272325
{
326+
if (pimpl_->threadExlusive.exchange(true)) {
327+
throw std::runtime_error(
328+
"ServerBase::Internal error, is_ready called, before take_data was called");
329+
}
330+
273331
// Mark as ready the entity from which we want to take data
274332
switch (static_cast<EntityType>(id)) {
275333
case EntityType::GoalService:
@@ -287,31 +345,36 @@ ServerBase::take_data_by_entity_id(size_t id)
287345
}
288346

289347
void
290-
ServerBase::execute(std::shared_ptr<void> & data)
348+
ServerBase::execute(std::shared_ptr<void> & dataIn)
291349
{
292-
if (!data && !pimpl_->goal_expired_.load()) {
293-
throw std::runtime_error("'data' is empty");
294-
}
295-
296-
if (pimpl_->goal_request_ready_.load()) {
297-
execute_goal_request_received(data);
298-
} else if (pimpl_->cancel_request_ready_.load()) {
299-
execute_cancel_request_received(data);
300-
} else if (pimpl_->result_request_ready_.load()) {
301-
execute_result_request_received(data);
302-
} else if (pimpl_->goal_expired_.load()) {
303-
execute_check_expired_goals();
304-
} else {
305-
throw std::runtime_error("Executing action server but nothing is ready");
306-
}
350+
std::shared_ptr<ServerBaseData> dataPtr = std::static_pointer_cast<ServerBaseData>(dataIn);
351+
352+
std::visit(
353+
[&](auto && data) -> void {
354+
using T = std::decay_t<decltype(data)>;
355+
if constexpr (std::is_same_v<T, ServerBaseData::GoalRequestData>) {
356+
execute_goal_request_received(
357+
std::get<0>(data), std::get<1>(data), std::get<2>(data),
358+
std::get<3>(data));
359+
}
360+
if constexpr (std::is_same_v<T, ServerBaseData::CancelRequestData>) {
361+
execute_cancel_request_received(std::get<0>(data), std::get<1>(data), std::get<2>(data));
362+
}
363+
if constexpr (std::is_same_v<T, ServerBaseData::ResultRequestData>) {
364+
execute_result_request_received(std::get<0>(data), std::get<1>(data), std::get<2>(data));
365+
}
366+
if constexpr (std::is_same_v<T, ServerBaseData::GoalExpiredData>) {
367+
execute_check_expired_goals();
368+
}
369+
},
370+
dataPtr->data);
307371
}
308372

309373
void
310-
ServerBase::execute_goal_request_received(std::shared_ptr<void> & data)
374+
ServerBase::execute_goal_request_received(
375+
rcl_ret_t ret, rcl_action_goal_info_t goal_info, rmw_request_id_t request_header,
376+
const std::shared_ptr<void> message)
311377
{
312-
auto shared_ptr = std::static_pointer_cast
313-
<std::tuple<rcl_ret_t, rcl_action_goal_info_t, rmw_request_id_t, std::shared_ptr<void>>>(data);
314-
rcl_ret_t ret = std::get<0>(*shared_ptr);
315378
if (RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) {
316379
// Ignore take failure because connext fails if it receives a sample without valid data.
317380
// This happens when a client shuts down and connext receives a sample saying the client is
@@ -320,14 +383,6 @@ ServerBase::execute_goal_request_received(std::shared_ptr<void> & data)
320383
} else if (RCL_RET_OK != ret) {
321384
rclcpp::exceptions::throw_from_rcl_error(ret);
322385
}
323-
rcl_action_goal_info_t goal_info = std::get<1>(*shared_ptr);
324-
rmw_request_id_t request_header = std::get<2>(*shared_ptr);
325-
std::shared_ptr<void> message = std::get<3>(*shared_ptr);
326-
327-
bool expected = true;
328-
if (!pimpl_->goal_request_ready_.compare_exchange_strong(expected, false)) {
329-
return;
330-
}
331386

332387
GoalUUID uuid = get_goal_id_from_goal_request(message.get());
333388
convert(uuid, &goal_info);
@@ -396,16 +451,13 @@ ServerBase::execute_goal_request_received(std::shared_ptr<void> & data)
396451
// Tell user to start executing action
397452
call_goal_accepted_callback(handle, uuid, message);
398453
}
399-
data.reset();
400454
}
401455

402456
void
403-
ServerBase::execute_cancel_request_received(std::shared_ptr<void> & data)
457+
ServerBase::execute_cancel_request_received(
458+
rcl_ret_t ret, std::shared_ptr<action_msgs::srv::CancelGoal::Request> request,
459+
rmw_request_id_t request_header)
404460
{
405-
auto shared_ptr = std::static_pointer_cast
406-
<std::tuple<rcl_ret_t, std::shared_ptr<action_msgs::srv::CancelGoal::Request>,
407-
rmw_request_id_t>>(data);
408-
auto ret = std::get<0>(*shared_ptr);
409461
if (RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) {
410462
// Ignore take failure because connext fails if it receives a sample without valid data.
411463
// This happens when a client shuts down and connext receives a sample saying the client is
@@ -414,9 +466,6 @@ ServerBase::execute_cancel_request_received(std::shared_ptr<void> & data)
414466
} else if (RCL_RET_OK != ret) {
415467
rclcpp::exceptions::throw_from_rcl_error(ret);
416468
}
417-
auto request = std::get<1>(*shared_ptr);
418-
auto request_header = std::get<2>(*shared_ptr);
419-
pimpl_->cancel_request_ready_ = false;
420469

421470
// Convert c++ message to C message
422471
rcl_action_cancel_request_t cancel_request = rcl_action_get_zero_initialized_cancel_request();
@@ -486,15 +535,13 @@ ServerBase::execute_cancel_request_received(std::shared_ptr<void> & data)
486535
if (RCL_RET_OK != ret) {
487536
rclcpp::exceptions::throw_from_rcl_error(ret);
488537
}
489-
data.reset();
490538
}
491539

492540
void
493-
ServerBase::execute_result_request_received(std::shared_ptr<void> & data)
541+
ServerBase::execute_result_request_received(
542+
rcl_ret_t ret, std::shared_ptr<void> result_request,
543+
rmw_request_id_t request_header)
494544
{
495-
auto shared_ptr = std::static_pointer_cast
496-
<std::tuple<rcl_ret_t, std::shared_ptr<void>, rmw_request_id_t>>(data);
497-
auto ret = std::get<0>(*shared_ptr);
498545
if (RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) {
499546
// Ignore take failure because connext fails if it receives a sample without valid data.
500547
// This happens when a client shuts down and connext receives a sample saying the client is
@@ -503,10 +550,7 @@ ServerBase::execute_result_request_received(std::shared_ptr<void> & data)
503550
} else if (RCL_RET_OK != ret) {
504551
rclcpp::exceptions::throw_from_rcl_error(ret);
505552
}
506-
auto result_request = std::get<1>(*shared_ptr);
507-
auto request_header = std::get<2>(*shared_ptr);
508553

509-
pimpl_->result_request_ready_ = false;
510554
std::shared_ptr<void> result_response;
511555

512556
// check if the goal exists
@@ -542,7 +586,6 @@ ServerBase::execute_result_request_received(std::shared_ptr<void> & data)
542586
rclcpp::exceptions::throw_from_rcl_error(rcl_ret);
543587
}
544588
}
545-
data.reset();
546589
}
547590

548591
void

0 commit comments

Comments
 (0)