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

feat(MultiThreadedExecutor): Added ability to handle exceptions from threads #2382

Closed
wants to merge 2 commits into from

Conversation

jmachowinski
Copy link
Contributor

This commit adds external exception handling for the worker threads, allowing application code to implement custom exception handling.

@jmachowinski
Copy link
Contributor Author

@wjwwood @mjcarroll @fujitatomoya Ping, I would like to see this merged for jazzy

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.

@jmachowinski thanks for the PR. i think this is useful! a couple of minor comments. (if we merge this, i think it would be nice to update the demos/example aligned with this.)

rclcpp/src/rclcpp/executors/multi_threaded_executor.cpp Outdated Show resolved Hide resolved
@jmachowinski jmachowinski force-pushed the exception_handling branch 2 times, most recently from 51c3b85 to 7fd9c1a Compare February 13, 2024 17:14
@jmachowinski jmachowinski force-pushed the exception_handling branch 2 times, most recently from 68979e9 to e62d7f6 Compare March 7, 2024 14:19
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.

@alsora friendly ping.

@fujitatomoya
Copy link
Collaborator

@alsora can you check and resolve your comments above?

@fujitatomoya
Copy link
Collaborator

fujitatomoya commented Mar 29, 2024

@jmachowinski can you rebase to rolling? i would like to make sure CI is green with current HEAD especially at this release moment.

after rebase, i need to start CI with https://gist.githubusercontent.com/fujitatomoya/fef2fe491b4f10fd5120cc3907fab525/raw/a05837c1b3c2ab1de927d222ef4fcb68fcd82850/ros2.repos

@alsora
Copy link
Collaborator

alsora commented Apr 1, 2024

Looks good to me

@jmachowinski
Copy link
Contributor Author

@jmachowinski can you rebase to rolling? i would like to make sure CI is green with current HEAD especially at this release moment.

rebased, note this currently fails the pipline, as it uncovered the bug reported in #2474

@fujitatomoya
Copy link
Collaborator

@jmachowinski this is ready for CI with ros2/demos#670? just chekcing.

@mjcarroll
Copy link
Member

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

Janosch Machowinski and others added 2 commits April 13, 2024 12:29
…threads

This commit adds external exception handling for the worker threads,
allowing application code to implement custom exception handling.

feat(Executor): Added spin API with exception handler.


Signed-off-by: Janosch Machowinski <[email protected]>
Signed-off-by: Janosch Machowinski <[email protected]>
@jmachowinski
Copy link
Contributor Author

rebased to rolling

I don't know why the CI failed, it builds for me using this file:
https://github.com/jmachowinski/build_desc/raw/exeception_handling/ros2.repos

@fujitatomoya
Copy link
Collaborator

CI:

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

@wjwwood
Copy link
Member

wjwwood commented Apr 16, 2024

I need to make some changes before merging this, but I cannot push to this branch, so I'll be opening a new pr. Please don't merge this one.

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.

I am going to fix up this pr and run CI on it as an option to merge, but I really think that it would be preferable to have an exception handler callback as an optional part of the ExecutorOptions instead, passing it during construction and/or setting it after construction with a new method, rather than modifying all of these spin functions and causing down stream changes.

Please don't merge this version of the pr, see: #2505

Comment on lines +85 to +95
/**
* \sa rclcpp::Executor:spin() for more details
* \throws std::runtime_error when spin() called while already spinning
* @param exception_handler will be called for every exception in the processing threads
*
* The exception_handler can be called from multiple threads at the same time.
* The exception_handler shall rethrow the exception it if wants to terminate the program.
*/
RCLCPP_PUBLIC
virtual void
spin(const std::function<void(const std::exception & e)> & exception_handler) = 0;
Copy link
Member

Choose a reason for hiding this comment

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

I would have liked to have had a non-virtual spin(... exception_handler = nullptr), that called a virtual and protected spin_impl(... exception_handler) = 0, so that we didn't need two signatures. It would be easier to document and wouldn't require every executor to implement two functions, one of which is pretty much always be boilerplate calling the other.

However, I don't know if I have time to do that before the freeze, so I won't block this on it, but I wanted to make the comment somewhere.

/**
* \sa rclcpp::Executor:spin() for more details
* \throws std::runtime_error when spin() called while already spinning
* @param exception_handler will be called for every exception in the processing threads
Copy link
Member

Choose a reason for hiding this comment

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

We don't use \ everywhere (some other @ have slipped through reviews), but we should tend towards the desired state when possible, which is \ everywhere. I'll fix these up in my version of this pr.

Comment on lines +85 to +86
/**
* \sa rclcpp::Executor:spin() for more details
Copy link
Member

Choose a reason for hiding this comment

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

This needs a "brief", with a /// ... line before the block comment.

@@ -125,9 +125,14 @@ class TimersManager
/**
* @brief Starts a thread that takes care of executing the timers stored in this object.
* Function will throw an error if the timers thread was already running.
*
* @param exception_handler if valid, the execution of the timer will be done in a try catch block,
Copy link
Member

Choose a reason for hiding this comment

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

I'll leave this @, because this file uses that style, but the whole file should be updated (it mostly got a review pass for being in the experimental namespace.

@@ -395,6 +395,69 @@ Executor::execute_any_executable(AnyExecutable & any_exec)
any_exec.callback_group->can_be_taken_from().store(true);
}

template<typename Function>
void execute_guarded(
const Function & fun,
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: use full variable names

Suggested change
const Function & fun,
const Function & function,

Comment on lines +46 to +63

void
StaticSingleThreadedExecutor::spin(
const std::function<void(const std::exception & e)> & exception_handler)
{
if (spinning.exchange(true)) {
throw std::runtime_error("spin() called while already spinning");
}
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );

// This is essentially the contents of the rclcpp::Executor::wait_for_work method,
// except we need to keep the wait result to reproduce the StaticSingleThreadedExecutor
// behavior.
while (rclcpp::ok(this->context_) && spinning.load()) {
this->spin_once_impl(std::chrono::nanoseconds(-1), exception_handler);
}
}

Copy link
Member

Choose a reason for hiding this comment

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

We should not copy-paste like this, we should have one call the other...

spinner_ = std::thread(&rclcpp::executors::SingleThreadedExecutor::spin, executor);
spinner_ = std::thread([this]() {executor->spin();});
Copy link
Member

Choose a reason for hiding this comment

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

Why is this change needed?

Copy link
Member

Choose a reason for hiding this comment

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

I see it doesn't compile... this is the kind of change that suggests we should rethink the approach. The old style of calling it should still work I think. I'll leave it, but it's another point against this approach in my mind.

@fujitatomoya
Copy link
Collaborator

but I really think that it would be preferable to have an exception handler callback as an optional part of the ExecutorOptions instead

+1 for this.

@wjwwood
Copy link
Member

wjwwood commented Apr 16, 2024

Another concern I've got after trying to shape this up for merge is that there's no way to indicate if an exception should cause the executor to stop or not. You can re-throw, but doing that in a thread will trigger termination, which by default will head towards std::abort, but can be overridden see: https://en.cppreference.com/w/cpp/error/set_terminate

Instead, I'd like to have seen either a return value from the exception handler that can tell the executor to stop or throw from the main thread or something, and/or pass the executor into the exception handler so that it can cancel it if desired.

@wjwwood
Copy link
Member

wjwwood commented Apr 16, 2024

Closing in favor of the updated version #2505

@wjwwood wjwwood closed this Apr 16, 2024
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