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

API Changes for Multihtreaded Events Executor #2466

Closed
wants to merge 10 commits into from

Conversation

jmachowinski
Copy link
Contributor

These are the API changes needed in order to implement an external Multihtreaded Events Executor

@mjcarroll @alsora @fujitatomoya

Copy link
Collaborator

@alsora alsora left a comment

Choose a reason for hiding this comment

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

Looks good to me.
I would suggest to add unit-tests for the Clock object

@jmachowinski jmachowinski marked this pull request as draft March 25, 2024 17:46
@jmachowinski
Copy link
Contributor Author

Writing tests was a good suggestion, I screwed up the clock interface, it's not thread safe, I will redesign it...

@jmachowinski
Copy link
Contributor Author

Redesigned the clock sleep interruption. Can the reviewers have a good lock at the mutex/lock/wait construct, its hard to get it right without data races....

@jmachowinski jmachowinski marked this pull request as ready for review March 26, 2024 14:26
@jmachowinski
Copy link
Contributor Author

I'll need to add a way to terminate the clock and all sleeps as soon as the shutdown call was done.

Copy link
Collaborator

@fujitatomoya fujitatomoya left a comment

Choose a reason for hiding this comment

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

overall lgtm, a couple of minor comments.

rclcpp/include/rclcpp/clock.hpp Outdated Show resolved Hide resolved
rclcpp/test/rclcpp/test_clock.cpp Outdated Show resolved Hide resolved
@jmachowinski
Copy link
Contributor Author

rebased to rolling

@mjcarroll
Copy link
Member

Waiting for this to get in before running CI: ros2/rosidl_typesupport_fastrtps#117

@mjcarroll
Copy link
Member

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

@jmachowinski
Copy link
Contributor Author

rebased and (hopefully) fixed windows compile error

@alsora
Copy link
Collaborator

alsora commented Apr 10, 2024

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

@jmachowinski
Copy link
Contributor Author

Dropped the test refactoring.

@mjcarroll
Copy link
Member

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

@mjcarroll
Copy link
Member

@jmachowinski this needs a merge from rolling because of the added API

@jmachowinski
Copy link
Contributor Author

rebased. test_executors_timer_cancel_behavior is flaky on my machine. I hope this gets fixed by #2500

@mjcarroll
Copy link
Member

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

Copy link
Collaborator

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

@wjwwood
Copy link
Member

wjwwood commented Apr 12, 2024

I'm highly suspect of the changes to the Executor API to make it so you don't need to inherit from rclcpp::Executor. I don't think we should go this route until it's explained better why that's necessary.

@jmachowinski
Copy link
Contributor Author

rebased to rolling

Janosch Machowinski and others added 2 commits April 14, 2024 14:32
This is useful in case a second thread needs to wake up another
thread, that is sleeping using a clock.

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
This adds support for multiple threads waiting on the same
clock, while an shutdown is invoked.

Signed-off-by: Janosch Machowinski <j.machowinski@cellumation.com>
@jmachowinski
Copy link
Contributor Author

jmachowinski commented Apr 14, 2024

I reworked this PR. Now everything in rclcpp::Executor is made virtual.

I changed two function, that were template function, without any reason to normal functions.

I reworked the template function spin_until_future_complete, so that it can be fully overwritten by a subclass.

I added an constructor, that will not touch anything in the system.

@wjwwood can you do a review ?

This commit makes every public funciton virtual, and adds virtual impl
function for the existing template functions.

The goal of this commit is to be able to fully control the everything from
a derived class.

Signed-off-by: Janosch Machowinski <j.machowinski@cellumation.com>
Copy link
Member

@wjwwood wjwwood left a comment

Choose a reason for hiding this comment

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

Pretty close, had a few small style things and a suggestion to scope down the changes.

Also, I think there might still be some style issues? please run ament_uncrustify path/to/rclcpp --reformat if you haven't already (saw some if( that should be if ().

Comment on lines -245 to +256
template<typename RepT = int64_t, typename T = std::milli>
void
RCLCPP_PUBLIC
virtual void
spin_node_once(
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node,
std::chrono::duration<RepT, T> timeout = std::chrono::duration<RepT, T>(-1))
{
return spin_node_once_nanoseconds(
node,
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
);
}
const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node,
std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));

/// Convenience function which takes Node and forwards NodeBaseInterface.
template<typename NodeT = rclcpp::Node, typename RepT = int64_t, typename T = std::milli>
void
RCLCPP_PUBLIC
virtual void
spin_node_once(
std::shared_ptr<NodeT> node,
std::chrono::duration<RepT, T> timeout = std::chrono::duration<RepT, T>(-1))
{
return spin_node_once_nanoseconds(
node->get_node_base_interface(),
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
);
}
const std::shared_ptr<rclcpp::Node> & node,
std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));
Copy link
Member

Choose a reason for hiding this comment

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

Do you absolutely need to make these non-template? I'm afraid existing code may not compile against this if certain types of durations are passed.

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 don't really need to, but all other functions in the executor have the same signature and compile just fine. Any std::chono::duration type should auto convert to std::chrono::nanoseconds.

Copy link
Member

Choose a reason for hiding this comment

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

Arguably the others taking only nanoseconds is a probably a bug, see: https://stackoverflow.com/a/22362867

It seems like in C++20 we could have a function that takes std::chrono::duration<auto>, but we don't have that right now.

I'll remove this change in my fix up.

rclcpp/include/rclcpp/executor.hpp Outdated Show resolved Hide resolved
rclcpp/include/rclcpp/executor.hpp Outdated Show resolved Hide resolved
rclcpp/include/rclcpp/executor.hpp Outdated Show resolved Hide resolved
rclcpp/include/rclcpp/executor.hpp Outdated Show resolved Hide resolved
@@ -447,6 +403,10 @@ class Executor
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node,
std::chrono::nanoseconds timeout);

virtual FutureReturnCode spin_until_future_complete_impl(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
virtual FutureReturnCode spin_until_future_complete_impl(
RCLCPP_PUBLIC
virtual FutureReturnCode
spin_until_future_complete_impl(

Also, needs at least a minimal docstring.

Copy link
Member

Choose a reason for hiding this comment

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

As I said above, I'd consider dropping this change, but if you keep it, consider the name spin_until_future_complete_nanoseconds() since that would match other functions like spin_node_once() -> spin_node_once_nanoseconds().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We got two colliding styles here, there is also
spin_once -> spin_once_impl and spin_some -> spin_some_impl

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added docs

Copy link
Member

Choose a reason for hiding this comment

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

We got two colliding styles here, there is also
spin_once -> spin_once_impl and spin_some -> spin_some_impl

Yeah, I'll leave it as-is, but I think this is related to the templated functions that take duration vs just taking nanoseconds, I still believe the templated approach is better, and in that case the narrowing to the non-template protected methods are truly just to cast it down to nanoseconds, so that makes sense in those cases. Likely if I had noticed I would have suggesting the newer functions follow that pattern, but it's not that important now.

Comment on lines 369 to 361
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
// inside a callback executed by an executor.

// Check the future before entering the while loop.
// If the future is already complete, don't try to spin.
std::future_status status = future.wait_for(std::chrono::seconds(0));
if (status == std::future_status::ready) {
return FutureReturnCode::SUCCESS;
}

auto end_time = std::chrono::steady_clock::now();
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
timeout);
if (timeout_ns > std::chrono::nanoseconds::zero()) {
end_time += timeout_ns;
}
std::chrono::nanoseconds timeout_left = timeout_ns;

if (spinning.exchange(true)) {
throw std::runtime_error("spin_until_future_complete() called while already spinning");
return spin_until_future_complete_impl(std::chrono::duration_cast<std::chrono::nanoseconds>(
timeout), [&future] () {
return future.wait_for(std::chrono::seconds(0));
}
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
while (rclcpp::ok(this->context_) && spinning.load()) {
// Do one item of work.
spin_once_impl(timeout_left);

// Check if the future is set, return SUCCESS if it is.
status = future.wait_for(std::chrono::seconds(0));
if (status == std::future_status::ready) {
return FutureReturnCode::SUCCESS;
}
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
if (timeout_ns < std::chrono::nanoseconds::zero()) {
continue;
}
// Otherwise check if we still have time to wait, return TIMEOUT if not.
auto now = std::chrono::steady_clock::now();
if (now >= end_time) {
return FutureReturnCode::TIMEOUT;
}
// Subtract the elapsed time from the original timeout.
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
}

// The future did not complete before ok() returned false, return INTERRUPTED.
return FutureReturnCode::INTERRUPTED;
);
Copy link
Member

Choose a reason for hiding this comment

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

I'd recommend dropping this change for now. It's likely to collide with #2475 if we merge that one and I don't think it's absolutely necessary (smaller changes are easier to get in when there's a lot of activity).

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 would like to keep this change, as it allows me to use a second thread to wait for the future, instead of the current construct. But if you have a hard opinion on dropping it, I am open for it.

Comment on lines +53 to +60
Executor::Executor(const std::shared_ptr<rclcpp::Context> & context)
: spinning(false),
entities_need_rebuild_(true),
collector_(nullptr),
wait_set_({}, {}, {}, {}, {}, {}, context)
{
}

Copy link
Member

Choose a reason for hiding this comment

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

This is better (as a workaround), it will be easier (in my opinion) to deprecate when we implement a base class after the release, as opposed to re-restricting the functions that currently require the executor base class.

rclcpp/include/rclcpp/executor.hpp Outdated Show resolved Hide resolved
Comment on lines +72 to +73
// make sure the thread is already sleeping before we send the cancel
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Copy link
Member

Choose a reason for hiding this comment

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

This is kind of a weak assumption (that the thread will start within 100ms), it would be better if we verified the thread had started before continuing, though there would still be a race between setting a bool in the thread and the next line of the thread that does sleep_until() is called. Ideally we'd be able to see when the clock is sleeping somehow. But I don't think that's possible. Not sure anything should change, just wanted to point it out.

jmachowinski and others added 4 commits April 15, 2024 15:47
Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>
Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>
Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>
Co-authored-by: William Woodall <wjwwood@gmail.com>
Signed-off-by: jmachowinski <jmachowinski@users.noreply.github.com>
@jmachowinski
Copy link
Contributor Author

jmachowinski commented Apr 15, 2024

Pretty close, had a few small style things and a suggestion to scope down the changes.

Also, I think there might still be some style issues? please run ament_uncrustify path/to/rclcpp --reformat if you haven't already (saw some if( that should be if ().

I did that, uncrustify doesn't fix this (?). BTW, why are we not using clang-format ? In my experience it works way better then the uncrustify / cpplint construct.

Janosch Machowinski added 2 commits April 15, 2024 16:30
Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
This change allows it to use a second thread to wait for
the future to become ready.

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
@wjwwood
Copy link
Member

wjwwood commented Apr 16, 2024

I did that, uncrustify doesn't fix this (?).

I did this and if found the issues (and auto-formatted the fix when I used --reformat):

william@markook-ubuntu-2204:~/ros2_ws/src/ros2/rclcpp (git:wjwwood/executor_features:163f727)
$ ament_uncrustify .
...
Code style divergence in file 'rclcpp/test/rclcpp/test_clock.cpp':

--- rclcpp/test/rclcpp/test_clock.cpp
+++ rclcpp/test/rclcpp/test_clock.cpp.uncrustify
@@ -116,4 +116,4 @@
-        Clocks,
-        TestClockWakeup,
-        ::testing::Values(
-            RCL_SYSTEM_TIME, RCL_ROS_TIME, RCL_STEADY_TIME
+  Clocks,
+  TestClockWakeup,
+  ::testing::Values(
+    RCL_SYSTEM_TIME, RCL_ROS_TIME, RCL_STEADY_TIME
@@ -185,2 +185,3 @@
-  for(size_t nr = 0; nr < thread_finished.size(); nr++) {
-    threads.push_back(std::thread(
+  for (size_t nr = 0; nr < thread_finished.size(); nr++) {
+    threads.push_back(
+      std::thread(
@@ -189 +190 @@
-        // make sure the thread starts sleeping late
+          // make sure the thread starts sleeping late
@@ -192 +193 @@
-      }));
+        }));
@@ -198 +199 @@
-  for(const bool & finished : thread_finished) {
+  for (const bool & finished : thread_finished) {
@@ -209,2 +210,2 @@
-    for(const bool finished : thread_finished) {
-      if(!finished) {
+    for (const bool finished : thread_finished) {
+      if (!finished) {
@@ -219 +220 @@
-  for(const bool finished : thread_finished) {
+  for (const bool finished : thread_finished) {
@@ -223 +224 @@
-  for(auto & thread : threads) {
+  for (auto & thread : threads) {
...

Are you running it correctly?

@clalancette
Copy link
Contributor

BTW, why are we not using clang-format ? In my experience it works way better then the uncrustify / cpplint construct.

We've had many long, detailed conversations about this, some as recently as a month ago. While I won't rehash all of the arguments, the short of it is:

  1. It is not possible to configure clang-format to meet our preferred style.
  2. Even if we were to change our preferred style to meet what clang-format wants, we have literally hundreds of packages in the core that would need to be changed. And they would all have to be changed at once. And it would mean that we could no longer cleanly backport from Rolling to stable branches.

We have many real problems in the code, this doesn't seem to be one of them (in my opinion).

@wjwwood
Copy link
Member

wjwwood commented Apr 16, 2024

Closing in favor of #2506

@wjwwood wjwwood closed this Apr 16, 2024
@wjwwood
Copy link
Member

wjwwood commented Apr 16, 2024

better then the uncrustify / cpplint construct.

Also, for what it's worth, I think clang-tidy might overlap a bit with cpplint, but it wouldn't completely replace it. most projects that use cpplint also use clang-tidy or something similar along side it.

@jmachowinski
Copy link
Contributor Author

Are you running it correctly?

Here is a paste of my system, it is clearly not working, but why ?

https://pastebin.com/gGbPLdaA

@clalancette
Copy link
Contributor

Here is a paste of my system, it is clearly not working, but why ?

Oh, you know, we've recently updated to a new version of uncrustify (0.78.1), that does things slightly differently than the old way. What version of uncrustify are you using?

@jmachowinski
Copy link
Contributor Author

Oh, you know, we've recently updated to a new version of uncrustify (0.78.1), that does things slightly differently than the old way. What version of uncrustify are you using?

Recent installation, uncrustify_vendor installs 0.78.1f

$ uncrustify --version
Uncrustify-0.78.1_f

I even patched ament_uncrustify to check if it picked up the right version, and this one also reports Uncrustify-0.78.1_f

I have an Uncrustify-0.72.0_f installed by apt, but even if I uninstall this version, my system still reports no style divergence.

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.

7 participants