Skip to content
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

Merged
merged 11 commits into from
Nov 15, 2018
Merged

Conversation

hidmic
Copy link
Contributor

@hidmic hidmic commented Nov 2, 2018

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.

@hidmic hidmic requested review from jacobperron and sloretz November 2, 2018 16:12
@tfoote tfoote added the in progress Actively being worked on (Kanban column) label Nov 2, 2018
Copy link
Contributor

@sloretz sloretz left a 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;
Copy link
Contributor

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

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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?)

Copy link
Contributor

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

Copy link
Member

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.

Copy link
Contributor Author

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(
Copy link
Contributor

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.

Copy link
Contributor Author

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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel result -> get result

Copy link
Contributor Author

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) {
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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

Copy link
Contributor Author

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)) {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

@jacobperron jacobperron mentioned this pull request Nov 2, 2018
10 tasks
rcl_allocator_t allocator = goal_client_options->allocator;

char * goal_service_name = NULL;
ret = rcl_action_get_goal_service_name(
Copy link
Member

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).

Copy link
Contributor Author

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) {
Copy link
Member

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

Copy link
Contributor Author

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) {
Copy link
Member

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

Copy link
Contributor Author

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");
Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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");
Copy link
Member

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).

Copy link
Contributor Author

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)) {
Copy link
Member

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?

hidmic added a commit that referenced this pull request Nov 5, 2018
@hidmic
Copy link
Contributor Author

hidmic commented Nov 5, 2018

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 ID info from the type-erased message before we can start pushing more logic into this layer.

@sloretz I'll push some changes to ros2/rcl_interfaces#47 as a POC for a conversion function within generated rosidl_action_type_support_t instances. cc @apojomovsky as it impacts your ongoing work.

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) {
Copy link
Member

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;

Copy link
Contributor Author

@hidmic hidmic Nov 6, 2018

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.

@hidmic
Copy link
Contributor Author

hidmic commented Nov 6, 2018

On 011dc30, I assumed that:

  1. the client has to be hooked to the status channel, unconditionally.
  2. the wait set is used by the action client and the action client alone (same would go for the action server).

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.

@hidmic hidmic changed the title [DO NOT MERGE] Action client first implementation Action client first implementation Nov 12, 2018
@hidmic hidmic force-pushed the hidmic/action-client-implementation branch from 208d017 to 7efd1a1 Compare November 12, 2018 17:29
@hidmic
Copy link
Contributor Author

hidmic commented Nov 12, 2018

@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.

@hidmic
Copy link
Contributor Author

hidmic commented Nov 12, 2018

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.

@hidmic hidmic changed the title Action client first implementation Action client implementation Nov 12, 2018
Copy link
Member

@jacobperron jacobperron left a 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) {
Copy link
Member

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:

rcl/rcl/src/rcl/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.

Copy link
Contributor Author

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(
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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.

Copy link
Contributor Author

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.
hidmic added a commit that referenced this pull request Nov 13, 2018
hidmic added a commit that referenced this pull request Nov 13, 2018
* Remove redundant null check for given rcl_node_t pointer.
* Always deallocate rcl_action_client_t pimpl on finalization.
* Minor formatting fixes.
@hidmic hidmic added the in review Waiting for review (Kanban column) label Nov 13, 2018
@hidmic hidmic removed the in progress Actively being worked on (Kanban column) label Nov 13, 2018
@hidmic
Copy link
Contributor Author

hidmic commented Nov 13, 2018

@jacobperron since integration tests will likely need rcl_wait support for both client and server, let's move forward with this and get back to them when we get to refactor wait set logic for the client to use groups. What do you think?

@jacobperron
Copy link
Member

since integration tests will likely need rcl_wait support for both client and server, let's move forward with this and get back to them when we get to refactor wait set logic for the client to use groups. What do you think?

@hidmic
Sounds good. I've created a task for adding integration tests: #328

Copy link
Contributor

@sloretz sloretz left a 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 78168bb.

Copy link
Member

@jacobperron jacobperron left a 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);


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra blank line

Copy link
Contributor Author

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.

@hidmic
Copy link
Contributor Author

hidmic commented Nov 14, 2018

@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 :( ) ?

@sloretz
Copy link
Contributor

sloretz commented Nov 14, 2018

CI up to rcl_action, testing only rcl_action

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Copy link
Member

@jacobperron jacobperron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@hidmic
Copy link
Contributor Author

hidmic commented Nov 15, 2018

Alright! Let's get this one in.

@hidmic hidmic merged commit f256da0 into master Nov 15, 2018
@tfoote tfoote removed the in review Waiting for review (Kanban column) label Nov 15, 2018
nburek pushed a commit to nburek/rcl that referenced this pull request Nov 26, 2018
nburek pushed a commit to nburek/rcl that referenced this pull request Nov 26, 2018
* Remove redundant null check for given rcl_node_t pointer.
* Always deallocate rcl_action_client_t pimpl on finalization.
* Minor formatting fixes.
nburek added a commit to nburek/rcl that referenced this pull request Nov 26, 2018
nburek added a commit to nburek/rcl that referenced this pull request Nov 26, 2018
nburek added a commit to nburek/rcl that referenced this pull request Nov 26, 2018
nburek added a commit to nburek/rcl that referenced this pull request Nov 26, 2018
ament_lint_auto_find_test_dependencies()
ament_find_gtest()
# Gtests
ament_add_gtest(test_action_client
test/rcl_action/test_action_client.cpp
)
Copy link
Member

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.

@dirk-thomas
Copy link
Member

@hidmic Is this branch still necessary or can it be deleted?

@hidmic hidmic deleted the hidmic/action-client-implementation branch November 27, 2018 04:06
@hidmic
Copy link
Contributor Author

hidmic commented Nov 27, 2018

@dirk-thomas branch deleted, thanks for the FYI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants