diff --git a/rcl_action/include/rcl_action/wait.h b/rcl_action/include/rcl_action/wait.h
index 29bdeb341..653e0af72 100644
--- a/rcl_action/include/rcl_action/wait.h
+++ b/rcl_action/include/rcl_action/wait.h
@@ -73,6 +73,53 @@ rcl_action_wait_set_add_action_client(
size_t * client_index,
size_t * subscription_index);
+/// Add a rcl_action_client_t to a wait set and get all entity indices.
+/**
+ * This function is similar to rcl_action_wait_set_add_action_client() but
+ * returns all five entity indices (3 clients + 2 subscriptions) instead of just
+ * the starting indices. This is useful when using
+ * rcl_action_client_wait_set_get_entities_ready_with_indices() to avoid race
+ * conditions in multi-threaded scenarios.
+ *
+ *
+ * Attribute | Adherence
+ * ------------------ | -------------
+ * Allocates Memory | Yes
+ * Thread-Safe | No
+ * Uses Atomics | No
+ * Lock-Free | Yes
+ *
+ * \param[inout] wait_set struct where action client service client and
+ * subscription are to be stored
+ * \param[in] action_client the action client to be added to the wait set
+ * \param[out] goal_client_index index of the goal client in the wait set.
+ * Optionally NULL.
+ * \param[out] cancel_client_index index of the cancel client in the wait set.
+ * Optionally NULL.
+ * \param[out] result_client_index index of the result client in the wait set.
+ * Optionally NULL.
+ * \param[out] feedback_subscription_index index of the feedback subscription.
+ * Optionally NULL.
+ * \param[out] status_subscription_index index of the status subscription.
+ * Optionally NULL.
+ * \return `RCL_RET_OK` if added successfully, or
+ * \return `RCL_RET_WAIT_SET_INVALID` if the wait set is zero initialized, or
+ * \return `RCL_RET_WAIT_SET_FULL` if the subscription set is full, or
+ * \return `RCL_RET_ACTION_CLIENT_INVALID` if the action client is invalid, or
+ * \return `RCL_RET_ERROR` if an unspecified error occurs.
+ */
+RCL_ACTION_PUBLIC
+RCL_WARN_UNUSED
+rcl_ret_t
+rcl_action_wait_set_add_action_client_with_indices(
+ rcl_wait_set_t * wait_set,
+ const rcl_action_client_t * action_client,
+ size_t * goal_client_index,
+ size_t * cancel_client_index,
+ size_t * result_client_index,
+ size_t * feedback_subscription_index,
+ size_t * status_subscription_index);
+
/// Add a rcl_action_server_t to a wait set.
/**
* This function will add the underlying services to the wait set.
@@ -238,6 +285,69 @@ rcl_action_client_wait_set_get_entities_ready(
bool * is_cancel_response_ready,
bool * is_result_response_ready);
+/// Get the wait set entities that are ready for a rcl_action_client_t using
+/// explicit indices.
+/**
+ * This is a thread-safe variant of
+ * rcl_action_client_wait_set_get_entities_ready() that accepts explicit wait
+ * set indices rather than reading them from the action client's internal state.
+ * This is useful when multiple threads use different wait sets with the same
+ * action client, avoiding the race condition where indices from one wait set
+ * could be used with a different wait set.
+ *
+ * The caller should obtain the indices from
+ * rcl_action_wait_set_add_action_client().
+ *
+ *
+ * Attribute | Adherence
+ * ------------------ | -------------
+ * Allocates Memory | No
+ * Thread-Safe | Yes (when used with per-wait_set indices)
+ * Uses Atomics | No
+ * Lock-Free | Yes
+ *
+ * \param[in] wait_set struct where action server services are to be stored
+ * \param[in] action_client an action client to query
+ * \param[in] feedback_subscription_index index of the feedback subscription in
+ * the wait set
+ * \param[in] status_subscription_index index of the status subscription in the
+ * wait set
+ * \param[in] goal_client_index index of the goal client in the wait set
+ * \param[in] cancel_client_index index of the cancel client in the wait set
+ * \param[in] result_client_index index of the result client in the wait set
+ * \param[out] is_feedback_ready `true` if there is a feedback message ready to
+ * take, `false` otherwise
+ * \param[out] is_status_ready `true` if there is a status message ready to
+ * take, `false` otherwise
+ * \param[out] is_goal_response_ready `true` if there is a goal response message
+ * ready to take, `false` otherwise
+ * \param[out] is_cancel_response_ready `true` if there is a cancel response
+ * message ready to take, `false` otherwise
+ * \param[out] is_result_response_ready `true` if there is a result response
+ * message ready to take, `false` otherwise
+ * \return `RCL_RET_OK` if call is successful, or
+ * \return `RCL_RET_WAIT_SET_INVALID` if the wait set is invalid, or
+ * \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
+ * \return `RCL_RET_ACTION_CLIENT_INVALID` if the action client is invalid, or
+ * \return `RCL_RET_ERROR` if an unspecified error occurs.
+ */
+RCL_ACTION_PUBLIC
+RCL_WARN_UNUSED
+rcl_ret_t
+rcl_action_client_wait_set_get_entities_ready_with_indices(
+ const rcl_wait_set_t * wait_set,
+ const rcl_action_client_t * action_client,
+ size_t feedback_subscription_index,
+ size_t status_subscription_index,
+ size_t goal_client_index,
+ size_t cancel_client_index,
+ size_t result_client_index,
+ bool * is_feedback_ready,
+ bool * is_status_ready,
+ bool * is_goal_response_ready,
+ bool * is_cancel_response_ready,
+ bool * is_result_response_ready);
+
/// Get the wait set entities that are ready for a rcl_action_server_t.
/**
* The caller can use this function to determine the relevant action server functions
diff --git a/rcl_action/src/rcl_action/action_client.c b/rcl_action/src/rcl_action/action_client.c
index 3a9bd07de..3d44dbc29 100644
--- a/rcl_action/src/rcl_action/action_client.c
+++ b/rcl_action/src/rcl_action/action_client.c
@@ -39,9 +39,7 @@ extern "C"
#include "rmw/qos_profiles.h"
#include "rmw/types.h"
-
-rcl_action_client_t
-rcl_action_get_zero_initialized_client(void)
+rcl_action_client_t rcl_action_get_zero_initialized_client(void)
{
static rcl_action_client_t null_action_client = {0};
return null_action_client;
@@ -689,11 +687,132 @@ rcl_action_client_wait_set_get_entities_ready(
return RCL_RET_OK;
}
-rcl_ret_t
-rcl_action_client_set_goal_client_callback(
- const rcl_action_client_t * action_client,
- rcl_event_callback_t callback,
- const void * user_data)
+rcl_ret_t rcl_action_wait_set_add_action_client_with_indices(
+ rcl_wait_set_t *wait_set, const rcl_action_client_t *action_client,
+ size_t *goal_client_index, size_t *cancel_client_index,
+ size_t *result_client_index, size_t *feedback_subscription_index,
+ size_t *status_subscription_index)
+{
+ rcl_ret_t ret;
+ RCL_CHECK_ARGUMENT_FOR_NULL(wait_set, RCL_RET_WAIT_SET_INVALID);
+ if (!rcl_action_client_is_valid(action_client)) {
+ return RCL_RET_ACTION_CLIENT_INVALID; // error already set
+ }
+
+ // Wait on action goal service response messages.
+ ret = rcl_wait_set_add_client(wait_set, &action_client->impl->goal_client,
+ &action_client->impl->wait_set_goal_client_index);
+ if (RCL_RET_OK != ret) {
+ return ret;
+ }
+ // Wait on action cancel service response messages.
+ ret = rcl_wait_set_add_client(wait_set, &action_client->impl->cancel_client,
+ &action_client->impl->wait_set_cancel_client_index);
+ if (RCL_RET_OK != ret) {
+ return ret;
+ }
+ // Wait on action result service response messages.
+ ret = rcl_wait_set_add_client(wait_set, &action_client->impl->result_client,
+ &action_client->impl->wait_set_result_client_index);
+ if (RCL_RET_OK != ret) {
+ return ret;
+ }
+ // Wait on action feedback messages.
+ ret = rcl_wait_set_add_subscription(
+ wait_set, &action_client->impl->feedback_subscription,
+ &action_client->impl->wait_set_feedback_subscription_index);
+ if (RCL_RET_OK != ret) {
+ return ret;
+ }
+ // Wait on action status messages.
+ ret = rcl_wait_set_add_subscription(
+ wait_set, &action_client->impl->status_subscription, &action_client->impl->wait_set_status_subscription_index);
+ if (RCL_RET_OK != ret) {
+ return ret;
+ }
+
+ // Return all indices to caller
+ if (NULL != goal_client_index) {
+ *goal_client_index = action_client->impl->wait_set_goal_client_index;
+ }
+ if (NULL != cancel_client_index) {
+ *cancel_client_index = action_client->impl->wait_set_cancel_client_index;
+ }
+ if (NULL != result_client_index) {
+ *result_client_index = action_client->impl->wait_set_result_client_index;
+ }
+ if (NULL != feedback_subscription_index) {
+ *feedback_subscription_index = action_client->impl->wait_set_feedback_subscription_index;
+ }
+ if (NULL != status_subscription_index) {
+ *status_subscription_index = action_client->impl->wait_set_status_subscription_index;
+ }
+ return RCL_RET_OK;
+}
+
+rcl_ret_t rcl_action_client_wait_set_get_entities_ready_with_indices(
+ const rcl_wait_set_t *wait_set, const rcl_action_client_t *action_client,
+ size_t feedback_subscription_index, size_t status_subscription_index,
+ size_t goal_client_index, size_t cancel_client_index,
+ size_t result_client_index, bool *is_feedback_ready, bool *is_status_ready,
+ bool *is_goal_response_ready, bool *is_cancel_response_ready,
+ bool *is_result_response_ready)
+{
+ RCL_CHECK_ARGUMENT_FOR_NULL(wait_set, RCL_RET_WAIT_SET_INVALID);
+ if (!rcl_action_client_is_valid(action_client)) {
+ return RCL_RET_ACTION_CLIENT_INVALID; // error already set
+ }
+ RCL_CHECK_ARGUMENT_FOR_NULL(is_feedback_ready, RCL_RET_INVALID_ARGUMENT);
+ RCL_CHECK_ARGUMENT_FOR_NULL(is_status_ready, RCL_RET_INVALID_ARGUMENT);
+ RCL_CHECK_ARGUMENT_FOR_NULL(is_goal_response_ready, RCL_RET_INVALID_ARGUMENT);
+ RCL_CHECK_ARGUMENT_FOR_NULL(is_cancel_response_ready,
+ RCL_RET_INVALID_ARGUMENT);
+ RCL_CHECK_ARGUMENT_FOR_NULL(is_result_response_ready,
+ RCL_RET_INVALID_ARGUMENT);
+
+ // Bounds checking using provided indices
+ if (feedback_subscription_index >= wait_set->size_of_subscriptions) {
+ RCL_SET_ERROR_MSG(
+ "wait set index for feedback subscription is out of bounds");
+ return RCL_RET_ERROR;
+ }
+ if (status_subscription_index >= wait_set->size_of_subscriptions) {
+ RCL_SET_ERROR_MSG(
+ "wait set index for status subscription is out of bounds");
+ return RCL_RET_ERROR;
+ }
+ if (goal_client_index >= wait_set->size_of_clients) {
+ RCL_SET_ERROR_MSG("wait set index for goal client is out of bounds");
+ return RCL_RET_ERROR;
+ }
+ if (cancel_client_index >= wait_set->size_of_clients) {
+ RCL_SET_ERROR_MSG("wait set index for cancel client is out of bounds");
+ return RCL_RET_ERROR;
+ }
+ if (result_client_index >= wait_set->size_of_clients) {
+ RCL_SET_ERROR_MSG("wait set index for result client is out of bounds");
+ return RCL_RET_ERROR;
+ }
+
+ const rcl_action_client_impl_t *impl = action_client->impl;
+ const rcl_subscription_t *feedback_subscription =
+ wait_set->subscriptions[feedback_subscription_index];
+ const rcl_subscription_t *status_subscription =
+ wait_set->subscriptions[status_subscription_index];
+ const rcl_client_t *goal_client = wait_set->clients[goal_client_index];
+ const rcl_client_t *cancel_client = wait_set->clients[cancel_client_index];
+ const rcl_client_t *result_client = wait_set->clients[result_client_index];
+ *is_feedback_ready = (&impl->feedback_subscription == feedback_subscription);
+ *is_status_ready = (&impl->status_subscription == status_subscription);
+ *is_goal_response_ready = (&impl->goal_client == goal_client);
+ *is_cancel_response_ready = (&impl->cancel_client == cancel_client);
+ *is_result_response_ready = (&impl->result_client == result_client);
+ return RCL_RET_OK;
+}
+
+rcl_ret_t rcl_action_client_set_goal_client_callback(
+ const rcl_action_client_t *action_client, rcl_event_callback_t callback,
+ const void *user_data)
{
if (!rcl_action_client_is_valid(action_client)) {
return RCL_RET_ACTION_CLIENT_INVALID;
diff --git a/rcl_action/test/rcl_action/test_wait.cpp b/rcl_action/test/rcl_action/test_wait.cpp
index 3529eef19..4f214c431 100644
--- a/rcl_action/test/rcl_action/test_wait.cpp
+++ b/rcl_action/test/rcl_action/test_wait.cpp
@@ -818,3 +818,290 @@ TEST_F(TestActionServerWait, test_server_wait_set_get_entities_ready) {
EXPECT_TRUE(is_result_request_ready);
EXPECT_TRUE(is_goal_expired);
}
+
+// Tests for new thread-safe functions added to fix issue #1455
+
+TEST_F(TestActionClientWait, test_wait_set_add_action_client_with_indices) {
+ // Test null wait_set
+ size_t goal_client_index = 42;
+ size_t cancel_client_index = 42;
+ size_t result_client_index = 42;
+ size_t feedback_subscription_index = 42;
+ size_t status_subscription_index = 42;
+
+ rcl_ret_t ret = rcl_action_wait_set_add_action_client_with_indices(
+ nullptr, &action_client, &goal_client_index, &cancel_client_index,
+ &result_client_index, &feedback_subscription_index,
+ &status_subscription_index);
+ EXPECT_EQ(RCL_RET_WAIT_SET_INVALID, ret);
+ EXPECT_EQ(42u, goal_client_index);
+ EXPECT_EQ(42u, cancel_client_index);
+ EXPECT_EQ(42u, result_client_index);
+ EXPECT_EQ(42u, feedback_subscription_index);
+ EXPECT_EQ(42u, status_subscription_index);
+ rcl_reset_error();
+
+ rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
+ EXPECT_EQ(RCL_RET_OK, rcl_wait_set_fini(&wait_set))
+ << rcl_get_error_string().str;
+ });
+
+ // Test null action_client
+ ret = rcl_action_wait_set_add_action_client_with_indices(
+ &wait_set, nullptr, &goal_client_index, &cancel_client_index,
+ &result_client_index, &feedback_subscription_index,
+ &status_subscription_index);
+ EXPECT_EQ(RCL_RET_ACTION_CLIENT_INVALID, ret);
+ EXPECT_EQ(42u, goal_client_index);
+ rcl_reset_error();
+
+ // Test with wait_set that's too small (no capacity)
+ ret = rcl_wait_set_init(&wait_set, 0, 0, 0, 0, 0, 0, &this->context,
+ rcl_get_default_allocator());
+ EXPECT_EQ(RCL_RET_OK, ret);
+
+ ret = rcl_action_wait_set_add_action_client_with_indices(
+ &wait_set, &action_client, &goal_client_index, &cancel_client_index,
+ &result_client_index, &feedback_subscription_index,
+ &status_subscription_index);
+ EXPECT_EQ(RCL_RET_WAIT_SET_FULL, ret);
+ EXPECT_TRUE(rcl_error_is_set());
+ rcl_reset_error();
+
+ // Typical case - wait_set with enough capacity (2 subscriptions, 3 clients)
+ EXPECT_EQ(RCL_RET_OK, rcl_wait_set_fini(&wait_set))
+ << rcl_get_error_string().str;
+ ret = rcl_wait_set_init(&wait_set, 2, 0, 0, 3, 0, 0, &this->context,
+ rcl_get_default_allocator());
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ ret = rcl_action_wait_set_add_action_client_with_indices(
+ &wait_set, &action_client, &goal_client_index, &cancel_client_index,
+ &result_client_index, &feedback_subscription_index,
+ &status_subscription_index);
+ EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ // Check that all indices were set (they should be 0, 1, 2 for clients, 0, 1
+ // for subscriptions)
+ EXPECT_EQ(0u, goal_client_index);
+ EXPECT_EQ(1u, cancel_client_index);
+ EXPECT_EQ(2u, result_client_index);
+ EXPECT_EQ(0u, feedback_subscription_index);
+ EXPECT_EQ(1u, status_subscription_index);
+
+ // Test with null index pointers (should still work)
+ EXPECT_EQ(RCL_RET_OK, rcl_wait_set_fini(&wait_set))
+ << rcl_get_error_string().str;
+ ret = rcl_wait_set_init(&wait_set, 2, 0, 0, 3, 0, 0, &this->context,
+ rcl_get_default_allocator());
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ ret = rcl_action_wait_set_add_action_client_with_indices(
+ &wait_set, &action_client, nullptr, nullptr, nullptr, nullptr, nullptr);
+ EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+}
+
+TEST_F(TestActionClientWait,
+ test_client_wait_set_get_entities_ready_with_indices) {
+ const char *action_name = "test_action_client_name";
+ const rosidl_action_type_support_t *action_typesupport =
+ ROSIDL_GET_ACTION_TYPE_SUPPORT(test_msgs, Fibonacci);
+ const rcl_action_client_options_t action_client_options =
+ rcl_action_client_get_default_options();
+
+ rcl_action_client_t action_client = rcl_action_get_zero_initialized_client();
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
+ rcl_ret_t fini_ret = rcl_action_client_fini(&action_client, &this->node);
+ EXPECT_EQ(RCL_RET_OK, fini_ret) << rcl_get_error_string().str;
+ });
+
+ rcl_ret_t ret =
+ rcl_action_client_init(&action_client, &this->node, action_typesupport,
+ action_name, &action_client_options);
+ EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
+ rcl_reset_error();
+
+ rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
+ EXPECT_EQ(RCL_RET_OK, rcl_wait_set_fini(&wait_set))
+ << rcl_get_error_string().str;
+ });
+ ret = rcl_wait_set_init(&wait_set, 2, 1, 1, 3, 1, 1, &this->context,
+ rcl_get_default_allocator());
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ bool is_feedback_ready = false;
+ bool is_status_ready = false;
+ bool is_goal_response_ready = false;
+ bool is_cancel_response_ready = false;
+ bool is_result_response_ready = false;
+
+ // Test null wait_set
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ nullptr, &action_client, 0, 1, 0, 1, 2, &is_feedback_ready,
+ &is_status_ready, &is_goal_response_ready, &is_cancel_response_ready,
+ &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_WAIT_SET_INVALID);
+ rcl_reset_error();
+
+ // Test null action_client
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, nullptr, 0, 1, 0, 1, 2, &is_feedback_ready, &is_status_ready,
+ &is_goal_response_ready, &is_cancel_response_ready,
+ &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_ACTION_CLIENT_INVALID);
+ rcl_reset_error();
+
+ // Test null output pointers
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 1, 0, 1, 2, nullptr, &is_status_ready,
+ &is_goal_response_ready, &is_cancel_response_ready,
+ &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_INVALID_ARGUMENT);
+ rcl_reset_error();
+
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 1, 0, 1, 2, &is_feedback_ready, nullptr,
+ &is_goal_response_ready, &is_cancel_response_ready,
+ &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_INVALID_ARGUMENT);
+ rcl_reset_error();
+
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 1, 0, 1, 2, &is_feedback_ready,
+ &is_status_ready, nullptr, &is_cancel_response_ready,
+ &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_INVALID_ARGUMENT);
+ rcl_reset_error();
+
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 1, 0, 1, 2, &is_feedback_ready,
+ &is_status_ready, &is_goal_response_ready, nullptr,
+ &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_INVALID_ARGUMENT);
+ rcl_reset_error();
+
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 1, 0, 1, 2, &is_feedback_ready,
+ &is_status_ready, &is_goal_response_ready, &is_cancel_response_ready,
+ nullptr);
+ EXPECT_EQ(ret, RCL_RET_INVALID_ARGUMENT);
+ rcl_reset_error();
+
+ // Test with out-of-bounds indices
+ wait_set.size_of_subscriptions = 1;
+ wait_set.size_of_clients = 1;
+
+ // feedback_subscription_index out of bounds
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 10, 0, 0, 0,
+ 0, // feedback index 10 is out of bounds
+ &is_feedback_ready, &is_status_ready, &is_goal_response_ready,
+ &is_cancel_response_ready, &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_ERROR);
+ rcl_reset_error();
+
+ // status_subscription_index out of bounds
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 10, 0, 0,
+ 0, // status index 10 is out of bounds
+ &is_feedback_ready, &is_status_ready, &is_goal_response_ready,
+ &is_cancel_response_ready, &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_ERROR);
+ rcl_reset_error();
+
+ // goal_client_index out of bounds
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 0, 10, 0,
+ 0, // goal client index 10 is out of bounds
+ &is_feedback_ready, &is_status_ready, &is_goal_response_ready,
+ &is_cancel_response_ready, &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_ERROR);
+ rcl_reset_error();
+
+ // cancel_client_index out of bounds
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 0, 0, 10,
+ 0, // cancel client index 10 is out of bounds
+ &is_feedback_ready, &is_status_ready, &is_goal_response_ready,
+ &is_cancel_response_ready, &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_ERROR);
+ rcl_reset_error();
+
+ // result_client_index out of bounds
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 0, 0, 0,
+ 10, // result client index 10 is out of bounds
+ &is_feedback_ready, &is_status_ready, &is_goal_response_ready,
+ &is_cancel_response_ready, &is_result_response_ready);
+ EXPECT_EQ(ret, RCL_RET_ERROR);
+ rcl_reset_error();
+
+ // Successful case with valid indices
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, 0, 0, 0, 0,
+ 0, // All indices are 0, which is valid for size 1
+ &is_feedback_ready, &is_status_ready, &is_goal_response_ready,
+ &is_cancel_response_ready, &is_result_response_ready);
+ EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+ // All entities should be not ready since the wait_set wasn't actually waited
+ // on
+ EXPECT_FALSE(is_feedback_ready);
+ EXPECT_FALSE(is_status_ready);
+ EXPECT_FALSE(is_goal_response_ready);
+ EXPECT_FALSE(is_cancel_response_ready);
+ EXPECT_FALSE(is_result_response_ready);
+}
+
+TEST_F(TestActionClientWait,
+ test_add_and_get_entities_ready_with_indices_consistency) {
+ // This test verifies that indices returned by add_action_client_with_indices
+ // can be correctly used with get_entities_ready_with_indices
+
+ rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
+ EXPECT_EQ(RCL_RET_OK, rcl_wait_set_fini(&wait_set))
+ << rcl_get_error_string().str;
+ });
+
+ // Initialize wait_set with enough capacity
+ rcl_ret_t ret = rcl_wait_set_init(&wait_set, 2, 0, 0, 3, 0, 0, &this->context,
+ rcl_get_default_allocator());
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ // Add action client and get indices
+ size_t goal_client_index = 0;
+ size_t cancel_client_index = 0;
+ size_t result_client_index = 0;
+ size_t feedback_subscription_index = 0;
+ size_t status_subscription_index = 0;
+
+ ret = rcl_action_wait_set_add_action_client_with_indices(
+ &wait_set, &action_client, &goal_client_index, &cancel_client_index,
+ &result_client_index, &feedback_subscription_index,
+ &status_subscription_index);
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ // Use those indices with get_entities_ready_with_indices
+ bool is_feedback_ready = false;
+ bool is_status_ready = false;
+ bool is_goal_response_ready = false;
+ bool is_cancel_response_ready = false;
+ bool is_result_response_ready = false;
+
+ ret = rcl_action_client_wait_set_get_entities_ready_with_indices(
+ &wait_set, &action_client, feedback_subscription_index,
+ status_subscription_index, goal_client_index, cancel_client_index,
+ result_client_index, &is_feedback_ready, &is_status_ready,
+ &is_goal_response_ready, &is_cancel_response_ready,
+ &is_result_response_ready);
+ EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ // All should be not ready (no data has been sent)
+ EXPECT_FALSE(is_feedback_ready);
+ EXPECT_FALSE(is_status_ready);
+ EXPECT_FALSE(is_goal_response_ready);
+ EXPECT_FALSE(is_cancel_response_ready);
+ EXPECT_FALSE(is_result_response_ready);
+}