-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Action client implementation #319
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be worth trying to push more logic into the C layer. Currently it looks like the API is pretty thin.
if (RCL_RET_SERVICE_NAME_INVALID == ret) { | ||
ret = RCL_RET_ACTION_NAME_INVALID; | ||
} else if (RCL_RET_BAD_ALLOC != ret) { | ||
ret = RCL_RET_ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about keeping RCL_RET_BAD_ALLOC
return code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
&impl->status_subscription, node, | ||
&type_support->status_topic_type_support, | ||
impl->action_name, &status_subscription_options); | ||
if (RCL_RET_OK == ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in cases like this goto error:
is ok to avoid nested if
statements. No need to change what's here though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about using goto
, but I went this route to simplify the rollback. Otherwise I would've needed a label for each failure stage (which things have I initialized so far that need finalization to keep things consistent?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think a label is needed for each failure stage. It looks like rcl_action_client_fini()
could be called instead of fini
-ing everything individually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of a label for each failure stage, check if each pointer is null and cleanup if it isn't.
I think a goto cleanup
and/or goto fail
(like in node.c) might be more readable. But I'm also okay with leaving this as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be consistent with rest of the codebase then :). Changed in c5dabd0.
RCUTILS_LOG_DEBUG_NAMED( | ||
ROS_PACKAGE_NAME, "Initializing client for action name '%s'", action_name); | ||
// Allocate space for the implementation struct. | ||
rcl_action_client_impl_t *impl = allocator->allocate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should error if NULL != action_client->impl
before allocating to avoid leaking memory if action_client
is already initialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Done in c5dabd0.
impl->action_name, &result_client_options); | ||
if (RCL_RET_OK == ret) { | ||
RCUTILS_LOG_DEBUG_NAMED( | ||
ROS_PACKAGE_NAME, "Action cancel result initialized"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cancel result
-> get result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in c5dabd0.
return RCL_RET_NODE_INVALID; | ||
} | ||
rcl_ret_t ret = RCL_RET_OK; | ||
if (action_client->impl) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the rcl_action_client_is_valid()
check above already checked this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True that. Fixed in c5dabd0.
if (rcl_client_is_valid(&impl->goal_client)) { | ||
ret = rcl_client_fini(&impl->goal_client, node); | ||
if (RCL_RET_OK != ret) { | ||
return RCL_RET_ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend returning the original error code here, same below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I tried to be consistent with rcl_action
documentation instead of propagating internal errors (that are usually hard to understand because of the lack of context). If we propagate, documentation needs an update.
// finalization failed, but it seems that's currently | ||
// not possible. | ||
rcl_action_client_impl_t * impl = action_client->impl; | ||
if (rcl_client_is_valid(&impl->goal_client)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably don't need this, rcl_client_fini
will check if the client is valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but there's an implicit purpose to this. If any of the five internal finalizations were to fail, some would end up finalized, some not. So this is an attempt to allow rcl_action_client_fini
to be called once again and not fail unless your action client was completely finalized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if there is a failure finalizing, then wouldn't the same failure happen again on subsequent calls to this function?
Maybe instead of returning early, we could cache the error result and continue trying to finalize the other objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then wouldn't the same failure happen again
That I don't think we can assume, some finalization errors come from deep into rmw
. But I do like the idea of finalizing as much as possible. Changed in c5dabd0.
rcl_allocator_t allocator = goal_client_options->allocator; | ||
|
||
char * goal_service_name = NULL; | ||
ret = rcl_action_get_goal_service_name( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this fits on one line (ditto for other rcl_action_get_*_service_name()
calls).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in c5dabd0.
allocator.deallocate(feedback_topic_name, allocator.state); | ||
|
||
if (RCL_RET_OK != ret) { | ||
if (RCL_RET_SERVICE_NAME_INVALID == ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RCL_RET_TOPIC_NAME_INVALID
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Fixed in c5dabd0.
allocator.deallocate(status_topic_name, allocator.state); | ||
|
||
if (RCL_RET_OK != ret) { | ||
if (RCL_RET_SERVICE_NAME_INVALID == ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RCL_RET_TOPIC_NAME_INVALID
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Fixed in c5dabd0.
const char * action_name, | ||
const rcl_action_client_options_t * options) | ||
{ | ||
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Initializing action client"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This debug statement is redundant with the one at L305.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeap, the first one is unnecessary. Fixed in c5dabd0.
&impl->status_subscription, node, | ||
&type_support->status_topic_type_support, | ||
impl->action_name, &status_subscription_options); | ||
if (RCL_RET_OK == ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of a label for each failure stage, check if each pointer is null and cleanup if it isn't.
I think a goto cleanup
and/or goto fail
(like in node.c) might be more readable. But I'm also okay with leaving this as is.
rcl_ret_t | ||
rcl_action_client_fini(rcl_action_client_t * action_client, rcl_node_t * node) | ||
{ | ||
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Finalizing action client"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: Other finalize functions in rcl distinguish between an invalid argument vs. invalid object. Consider adding
RCL_CHECK_ARGUMENT_FOR_NULL(action_client, RCL_RET_INVALID_ARGUMENT);
RCL_CHECK_ARGUMENT_FOR_NULL(node, RCL_RET_INVALID_ARGUMENT);
here before the *_is_valid()
checks.
This applies to the other functions as well (except init(),
is_valid()`, and the getters).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, and it makes sense. Fixed in c5dabd0.
// finalization failed, but it seems that's currently | ||
// not possible. | ||
rcl_action_client_impl_t * impl = action_client->impl; | ||
if (rcl_client_is_valid(&impl->goal_client)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if there is a failure finalizing, then wouldn't the same failure happen again on subsequent calls to this function?
Maybe instead of returning early, we could cache the error result and continue trying to finalize the other objects?
So, about making the API thicker. Per offline discussion with @sloretz and @jacobperron, I think we can agree that we need a mechanism to retrieve the goal @sloretz I'll push some changes to ros2/rcl_interfaces#47 as a POC for a conversion function within generated |
char * result_service_name = NULL; | ||
ret = rcl_action_get_result_service_name(action_name, allocator, &result_service_name); | ||
if (RCL_RET_OK != ret) { | ||
if (RCL_RET_BAD_ALLOC != ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be more clearly written as:
if (RCL_RET_BAD_ALLOC == ret) {
return RCL_RET_BAD_ALLOC;
}
return RCL_RET_ERROR;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, changed in f29d359.
On 011dc30, I assumed that:
I'm not so sure about (1), API documentation was not consistent about the need of status. (2) seems restrictive to me but a reasonable trade off until wait set groups are implemented. |
208d017
to
7efd1a1
Compare
@jacobperron I added basic tests. Let me know if/when we can move forward with having a single set of integration tests as suggested in https://github.com/ros2/rcl/pull/323/files#r232681489 and I'll add those. |
This PR still doesn't include any logic as that requires typesupport we do not yet have. I'd like to know what the rest have to say in tomorrow's retrospective and how https://github.com/ros2/rcl/pull/323/files#r232674670 unfolds. May be a neat path forward. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Couple nits and one discussion point re the finalize function.
I think it is okay to have a follow-up ticket to address making the status subscriber (and feedback) optional.
I think more about the wait set, but at first glance this seems like a reasonable compromise until wait set group support.
ret = RCL_RET_ERROR; | ||
} | ||
} | ||
if (RCL_RET_OK != ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be if (RCL_RET_OK == ret)
?
If there is a failure what are the chances that the caller will call finalize again? And if they do, what could change the outcome of this function? It seems better to me to deallocate as long as the pimpl is not null to avoid any memory leaks.
Similar to the other fini implementations, e.g. publisher.c
:
Lines 208 to 214 in 9351fd8
rmw_ret_t ret = | |
rmw_destroy_publisher(rmw_node, publisher->impl->rmw_handle); | |
if (ret != RMW_RET_OK) { | |
RCL_SET_ERROR_MSG(rmw_get_error_string().str); | |
result = RCL_RET_ERROR; | |
} | |
allocator.deallocate(publisher->impl, allocator.state); |
Even if there is an error in destroying the underlying rmw object, we still deallocate the pimpl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, let's be consistent. Fixed in 0c31382.
return RCL_RET_ACTION_CLIENT_INVALID; // error already set | ||
} | ||
// Wait on action goal service response messages. | ||
ret = rcl_wait_set_add_client( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: fit on single line. Same for other lines below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0c31382.
const rcl_action_client_options_t * options) | ||
{ | ||
RCL_CHECK_ARGUMENT_FOR_NULL(action_client, RCL_RET_INVALID_ARGUMENT); | ||
RCL_CHECK_ARGUMENT_FOR_NULL(node, RCL_RET_INVALID_ARGUMENT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rcl_node_is_valid()
already checks for a null pointer, so this line can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in 0c31382.
* Remove redundant null check for given rcl_node_t pointer. * Always deallocate rcl_action_client_t pimpl on finalization. * Minor formatting fixes.
* Remove redundant null check for given rcl_node_t pointer. * Always deallocate rcl_action_client_t pimpl on finalization. * Minor formatting fixes.
@jacobperron since integration tests will likely need |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with green CI and one minor comment
@@ -31,6 +31,8 @@ extern "C" | |||
#include "rcl/macros.h" | |||
#include "rcl/types.h" | |||
|
|||
#include "rosidl_generator_c/action_type_support_struct.h" | |||
typedef struct rosidl_action_type_support_t rosidl_action_type_support_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 78168bb.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, pending use of macros instead of the static functions as discussed (#323 (comment)) and green CI.
@@ -420,6 +415,7 @@ rcl_action_take_status( | |||
const rcl_action_client_t * action_client, | |||
void * ros_status_array); | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra blank line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh, I forgot about the macros. Fixed in c7e6183.
@apojomovsky @jacobperron @sloretz may I ask one of you guys to start CI on this one for me (nope, I can't do it myself :( ) ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
Alright! Let's get this one in. |
* Remove redundant null check for given rcl_node_t pointer. * Always deallocate rcl_action_client_t pimpl on finalization. * Minor formatting fixes.
This reverts commit 9e655ae.
This reverts commit 124aabb.
This reverts commit 9e655ae.
This reverts commit 124aabb.
ament_lint_auto_find_test_dependencies() | ||
ament_find_gtest() | ||
# Gtests | ||
ament_add_gtest(test_action_client | ||
test/rcl_action/test_action_client.cpp | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is only covering the default rmw impl. It would be good if the test would be invoked for every rmw impl.
@hidmic Is this branch still necessary or can it be deleted? |
@dirk-thomas branch deleted, thanks for the FYI. |
Connected to #306. This pull request introduces a first implementation of
rcl
's action client interface. It depends on ros2/rosidl#310 for basic typesupport declarations to compile and on ros2/rcl_interfaces#47 for basic typesupport implementation to test and thus it's still lacking proper test coverage.I'm pushing this early to trigger a follow up discussion on the pros and cons of the current fully type-erased action client API.