-
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
Add function for waiting on multiple wait sets together #330
Conversation
Internally, the function collates all wait sets into a single wait set that is waited on.
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! Left a few comments.
rcl/include/rcl/wait.h
Outdated
@@ -428,6 +427,119 @@ RCL_WARN_UNUSED | |||
rcl_ret_t | |||
rcl_wait(rcl_wait_set_t * wait_set, int64_t timeout); | |||
|
|||
/// Block until multiple wait sets are ready or until the timeout has been exceeded. | |||
/** | |||
* This function has similar behavior as rcl_wait(), but acts on multiple wait sets. |
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.
@jacobperron nit: similar behavior to?
@@ -435,3 +435,28 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), guard_condition) { | |||
EXPECT_EQ(RCL_RET_OK, f.get()); | |||
EXPECT_LE(std::abs(diff - trigger_diff.count()), TOLERANCE); | |||
} | |||
|
|||
// Test rcl_wait_multiple with a positive finite timeout value (1ms) | |||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), finite_timeout_multiple) { |
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.
@jacobperron shouldn't we add a few more tests? Like, for negative timeout, for zero timeout, for invalid arguments, for invalid wait sets, with a dummy guard and a dummy timer in different wait sets to make sure things are being collated and then updated back properly, and so on. Not to repeat rcl_wait()
tests, just to verify the new functionality is correct.
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, I meant to, but forgot.
rcl/src/rcl/wait.c
Outdated
size_t number_of_timers = 0u; | ||
size_t number_of_clients = 0u; | ||
size_t number_of_services = 0u; | ||
for (i = 0u; i < num_wait_sets; ++i) { |
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.
@jacobperron it could be that no wait set was provided, should we have a different rcl error message for that case (right now it'll say that wait sets are empty)?
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.
My intention was to return RCL_RET_WAIT_SET_EMPTY
thinking it is intuitive, but I can add a new return code e.g. RCL_RET_WAIT_SET_NONE
.
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.
Either that or RCL_RET_INVALID_ARGUMENT
plus RCL_SET_ERROR_MSG()
with a nice message.
rcl/src/rcl/wait.c
Outdated
number_of_clients == 0u && | ||
number_of_services == 0u) | ||
{ | ||
RCL_SET_ERROR_MSG("wait set is empty"); |
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.
@jacobperron consider "wait sets are empty"
instead.
rcl/src/rcl/wait.c
Outdated
{ | ||
RCL_CHECK_ARGUMENT_FOR_NULL(wait_sets, RCL_RET_INVALID_ARGUMENT); | ||
size_t i; | ||
size_t j; |
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.
@jacobperron nit: I have a slight preference for declaring loop variables within the for
statement. Won't block if you prefer to keep things C89-ish.
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 guess my head was in C89 mode. Since we're targeting C99, I'm happy to change it.
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 now why I implemented this way. The existing code in this file follows a similar style:
Lines 479 to 480 in f256da0
{ // scope to prevent i from colliding below | |
uint64_t i = 0; |
Line 556 in f256da0
size_t i; |
Although, as a result there's that awkward scope to prevent variable name collisions... Maybe we should change those instances as well for consistency.
rcl/src/rcl/wait.c
Outdated
rcl_guard_condition_t * guard_condition = rcl_timer_get_guard_condition(wait_set->timers[j]); | ||
if (NULL != guard_condition) { | ||
// rcl_wait() will take care of moving these backwards and setting guard_condition_count. | ||
const size_t index = wait_set->size_of_guard_conditions + (wait_set->impl->timer_index - 1); |
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.
@jacobperron not sure I follow how index
would take different values across multiple iterations for the same wait_set
.
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.
It won't, this is a copy-paste bug.
rcl/src/rcl/wait.c
Outdated
size_t collated_service_index = 0u; | ||
for (i = 0u; i < num_wait_sets; ++i) { | ||
wait_set = &wait_sets[i]; | ||
for (j = 0u; j < wait_set->size_of_timers; ++j) { |
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.
@jacobperron nit: fine to use for
loops, but we could use memcpy
too (that depending on the compiler and target architecture may be more performant).
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.
Sure, I can do that. Do you think there are any issues with readability (and correctness)?
memcpy(
wait_set->timers,
collated_wait_set.timers + collated_timer_index,
sizeof(rcl_timer_t *) * wait_set->size_of_timers);
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.
Ugh, that doesn't look nearly as clean as your current code. Nevermind, let's trust loop unrolling will work :).
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.
👍
@hidmic Thanks for the review! I think I've address all of your comments. Ready for another round of review. |
if (0u == num_wait_sets) { | ||
RCL_SET_ERROR_MSG("number of wait sets must be greater than zero"); | ||
return 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.
Could take a short cut and return rcl_wait()
if 1u == num_wait_sets
rcl/test/rcl/test_wait.cpp
Outdated
ret = rcl_wait_set_init(&wait_sets[i], 0, 1, 1, 0, 0, rcl_get_default_allocator()); | ||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str; | ||
|
||
// Add a dummy guard condition to avoid an 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.
What error is the dummy guard condition avoiding?
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.
That is a good question. I copied this over from the existing test involving a timer, without it the test segfaults 😨
I dug into it a bit more, and it seems like the rmw guard condition count isn't being incremented in rcl_wait_set_add_timer(). Adding a dummy guard condition appears to be a hack to get the count to increment...
I think it can be fixed easily by ensuring the rmw guard condition count is incremented when adding timers, but without understanding the reason for the current implementation I am hesitant to change it. @wjwwood, is this a bug or intentional?
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 know where it was copied from, but here maybe the error could be avoided if you also update the values passed to rcl_wait_set_init()
above?
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.
Nevermind, I see that the rmw guard condition count is incremented for timers in rcl_wait
as the comment says (my bad):
Line 502 in f256da0
++(rmw_gcs->guard_condition_count); |
Setting the number of guard conditions to zero resolves the issue. I've also updated the tests for rcl_wait()
that originally confused me. See e40a26b
4bde955
to
e40a26b
Compare
@jacobperron are we pushing this one as-is or are you considering a re-implementation in terms of the feature introduced by #335 ? |
Makes total sense to me, let's close it. |
Internally, the function collates all wait sets into a single wait set that is waited on.
Resolves #308