Skip to content

Log error instead of throwing exception in Transition and State reset(), mark no except#1297

Merged
brawner merged 2 commits intomasterfrom
brawner/rclcpp_lifecycle-catch-exception-destructor
Sep 11, 2020
Merged

Log error instead of throwing exception in Transition and State reset(), mark no except#1297
brawner merged 2 commits intomasterfrom
brawner/rclcpp_lifecycle-catch-exception-destructor

Conversation

@brawner
Copy link
Copy Markdown
Contributor

@brawner brawner commented Sep 10, 2020

This was discovered during fault injection testing, but if for some reason rcl_lifecycle_state_fini fails during rclcpp::Transition::reset() or rclcpp::State::reset(), these destructors will throw an uncatchable exception because destructors are noexcept by default.

There are other potential resolutions to this issue, like marking these destructors noexcept(false), but that has performance side effects by poisoning the chain of destructors that eventually call these.

Signed-off-by: Stephen Brawner brawner@gmail.com

@brawner brawner self-assigned this Sep 10, 2020
@clalancette
Copy link
Copy Markdown
Contributor

There are other potential resolutions to this issue, like marking these destructors noexcept(false), but that has performance side effects by poisoning the chain of destructors that eventually call these.

Good catch. I like this solution better than the marking of the destructors. I do have one comment, but I'll put it inline.

reset();
try {
reset();
} catch (...) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Catching any exception here is "safer", in that anything that happens to throw will be caught. But on the other hand, that same logic goes for every destructor in our codebase, and we don't wrap them all in try { } catch(...) {}. What I'm trying to get at here is that I think this may be better as:

} catch (const RCLErrorBase &) {

(looks deeper). Oh, but rclcpp::exception::throw_from_rcl_error can also throw a std::invalid_argument or std::runtime_error. That's unfortunate. Do you have any thoughts here on how we could avoid the catch-all, or do you think using the catch-all is the right solution?

Copy link
Copy Markdown
Collaborator

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 better to put try & catch for now, but i agree with @clalancette . although i do not have any better idea for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wasn't terribly convinced of any one solution. The catch all is pretty useful, since reset is a function that can throw several different types of exceptions, and it's simple to write. reset gets plenty of usage in the unit tests, so I think if it were to start throwing a different type of exception we'd start seeing it show up there.

Other ideas I had...

rcl_lifecycle_state_fini currently only fails if it's passed a bad allocator, which would be a programming error. But if it did fail, it would only leak memory. I could move the RCLCPP_ERROR message to reset() and just ignore a bad return value. In that case, it might also be good to make reset() noexcept as well, so other exceptions thrown from inside that function are also identified and resolved.

I could remove the fault injection hook from rcl_lifecycle_state_fini. It wouldn't decrease coverage below 95% in rcl_lifecycle or rclcpp_lifecycle I believe.

I could catch a std::exception, which RclError is derived from, and log the e.what(). That would be about just as general as the ... though.

I could catch the RCLErrorBase, and let the std::exceptions from throw_from_rcl_error still crash since they also represent programming errors but won't be thrown during fault injection testing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rcl_lifecycle_state_fini currently only fails if it's passed a bad allocator, which would be a programming error. But if it did fail, it would only leak memory. I could move the RCLCPP_ERROR message to reset() and just ignore a bad return value. In that case, it might also be good to make reset() noexcept as well, so other exceptions thrown from inside that function are also identified and resolved.

Oh, I like this option. It seems the cleanest to me. I don't think we'll run into any problems marking reset() as noexcept either; other than the throw_from_rcl_error, I don't think there is anything else in the method that can throw.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Switched to noexcept option

Signed-off-by: Stephen Brawner <brawner@gmail.com>
@brawner brawner force-pushed the brawner/rclcpp_lifecycle-catch-exception-destructor branch from 422d5c6 to f5709a4 Compare September 10, 2020 21:13
Signed-off-by: Stephen Brawner <brawner@gmail.com>
@brawner
Copy link
Copy Markdown
Contributor Author

brawner commented Sep 11, 2020

Testing --packages-select rclcpp_lifecycle

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

Copy link
Copy Markdown
Contributor

@clalancette clalancette left a comment

Choose a reason for hiding this comment

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

Great, thanks for iterating here. This change seems good to me with green CI.

@brawner brawner changed the title Catch potential exception in rclcpp_lifecycle::~State/~Transition and log Log error instead of throwing exception in Transition and State reset(), mark no except Sep 11, 2020
@brawner
Copy link
Copy Markdown
Contributor Author

brawner commented Sep 11, 2020

Thanks for the review @clalancette and @fujitatomoya

@brawner brawner merged commit 0276809 into master Sep 11, 2020
@delete-merged-branch delete-merged-branch bot deleted the brawner/rclcpp_lifecycle-catch-exception-destructor branch September 11, 2020 18:11
brawner added a commit that referenced this pull request Oct 5, 2020
…(), mark no except (#1297)

* Catch potential exception in destructor and log

Signed-off-by: Stephen Brawner <brawner@gmail.com>

* Remove thrown error from reset and mark it no except

Signed-off-by: Stephen Brawner <brawner@gmail.com>
brawner added a commit that referenced this pull request Oct 5, 2020
… and State reset() mark no except (#1297) (#1378)

* Log error instead of throwing exception in Transition and State reset(), mark no except (#1297)

* Catch potential exception in destructor and log

Signed-off-by: Stephen Brawner <brawner@gmail.com>

* Remove thrown error from reset and mark it no except

Signed-off-by: Stephen Brawner <brawner@gmail.com>

* Remove noexcept for ABI compatibility

Signed-off-by: Stephen Brawner <brawner@gmail.com>
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.

4 participants