Conversation
sloretz
left a comment
There was a problem hiding this comment.
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.
Same comment about keeping RCL_RET_BAD_ALLOC return code
| &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.
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.
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.
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.
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.
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.
Should error if NULL != action_client->impl before allocating to avoid leaking memory if action_client is already initialized.
| 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.
cancel result -> get result
| return RCL_RET_NODE_INVALID; | ||
| } | ||
| rcl_ret_t ret = RCL_RET_OK; | ||
| if (action_client->impl) { |
There was a problem hiding this comment.
I think the rcl_action_client_is_valid() check above already checked this
| 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.
Recommend returning the original error code here, same below
There was a problem hiding this comment.
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.
Probably don't need this, rcl_client_fini will check if the client is valid.
There was a problem hiding this comment.
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.
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.
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.
nit: this fits on one line (ditto for other rcl_action_get_*_service_name() calls).
| allocator.deallocate(feedback_topic_name, allocator.state); | ||
|
|
||
| if (RCL_RET_OK != ret) { | ||
| if (RCL_RET_SERVICE_NAME_INVALID == ret) { |
| allocator.deallocate(status_topic_name, allocator.state); | ||
|
|
||
| if (RCL_RET_OK != ret) { | ||
| if (RCL_RET_SERVICE_NAME_INVALID == ret) { |
| 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.
This debug statement is redundant with the one at L305.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
Might be more clearly written as:
if (RCL_RET_BAD_ALLOC == ret) {
return RCL_RET_BAD_ALLOC;
}
return RCL_RET_ERROR;|
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. |
jacobperron
left a comment
There was a problem hiding this comment.
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.
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
Even if there is an error in destroying the underlying rmw object, we still deallocate the pimpl.
There was a problem hiding this comment.
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.
nit: fit on single line. Same for other lines below.
| 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.
rcl_node_is_valid() already checks for a null pointer, so this line can be removed.
* 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 |
sloretz
left a comment
There was a problem hiding this comment.
LGTM with green CI and one minor comment
| #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.
jacobperron
left a comment
There was a problem hiding this comment.
LGTM, pending use of macros instead of the static functions as discussed (#323 (comment)) and green CI.
| const rcl_action_client_t * action_client, | ||
| void * ros_status_array); | ||
|
|
||
|
|
There was a problem hiding this comment.
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 :( ) ? |
|
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.
| # Gtests | ||
| ament_add_gtest(test_action_client | ||
| test/rcl_action/test_action_client.cpp | ||
| ) |
There was a problem hiding this comment.
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.