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

Error handling using std::error_code and the proposed std::expected type #801

Closed
wants to merge 1 commit into from

Conversation

cryptocode
Copy link
Contributor

Docs and rationale: https://github.com/cryptocode/raiblocks/wiki/Error-handling-in-the-node-using-std::error_code-and-std::expected

This PR applies the above error handling ideas to a handful of RPC handlers. A few functions with the double-response-write problem are fixed as well, using std::error_code.

I've put a few error codes in a common enum, which can be used elsewhere later on.

decode_unsigned is used to test std::expected which allows both values and errors to be returned. I think expected is a good fit for the node, but it's probably a good idea to introduce it gradually to see how well things compose.

Since expected isn't standardized yet (expected in C++20), I've included expected-light (a single-header file) which we'll remove once compilers ships with a std implementation.

Copy link
Contributor

@PlasmaPower PlasmaPower left a comment

Choose a reason for hiding this comment

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

Some more general thoughts:

  • It looks like you didn't transform some RPC functions, like block_create. Was there a reason for this?
  • Should we return an error code from the RPC functions?
  • Should we use something similar to the Rust try macro (now an operator) for preconditions, and maybe other places?
  • When std::expected is converted to a boolean, it returns true if it was successful. There's not much we can do about this, but it is the opposite of our previous idiom of errors being truthy.
  • Should we keep the success-first pattern in if statements? Personally I favor putting the error first so it's right next to the failing condition.

Overall I really like the direction of this PR.

rai/node/rpc.cpp Outdated
std::string account_text;

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: accidental whitespace change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, fixed.

rai::block_hash hash;
auto head_str (request.get_optional<std::string> ("head"));
rai::transaction transaction (node.store.environment, nullptr, false);
if (head_str)
{
error = hash.decode_hex (*head_str);
if (!error)
if (hash.decode_hex (*head_str))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this condition should be inverted if we're going with the success-first pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See my main comment about this topic.

if (destinations_text.is_initialized ())

auto destinations (decode_unsigned (request.get<std::string> ("destinations", "0")));
if (!ec && !destinations)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: in an above function (I don't remember which) you don't check ec when parsing future values. It doesn't matter that much, but we should probably be consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, though I don't seem to find the missing check.

@cryptocode
Copy link
Contributor Author

cryptocode commented May 15, 2018

It looks like you didn't transform some RPC functions, like block_create. Was there a reason for this?

Yeah, only a few functions are converted as I wanted to set up a path for using std::expected and get that reviewed. There's a lot of actions to be converted and tested properly.

Serg offered to help with the conversion once the framework is in place.

If you prefer this PR to do the full conversion of RPC instead of multiple PR's, I'm happy to do that, though it'll take some time for me to get around to that.

Should we return an error code from the RPC functions?

I was thinking that process_request can't do much about them anyway since the error response is already sent by the action handler.

One option would be to let process_request do the error response, but doing it in the action handler allows for some useful idioms, see end of blocks for an example.

Should we use something similar to the Rust try macro (now an operator) for preconditions, and maybe other places?

That would be great. P0779R0 is a proposal to implement this, which will work with std::expected. Not sure what a good C++ idiom is right now. There's also a monadic extension proposal to allow chaining of expected.

When std::expected is converted to a boolean, it returns true if it was successful. There's not much we can do about this, but it is the opposite of our previous idiom of errors being truthy.

Since std::expected is not an error, but a value or an error, I think it makes sense.

I see your point, though - it's going to be a bit painful to read the code base as the old idiom gets slowly replaced.

Should we keep the success-first pattern in if statements? Personally I favor putting the error first so it's right next to the failing condition.

I have no strong opinions here. Possibly the no-early-returns principle in the code base affected the decision to place errors at the end, since you're then forced to nest statements ?

@cryptocode cryptocode force-pushed the std-expected-rpc branch 2 times, most recently from 034269a to 334b243 Compare May 15, 2018 10:35
rai/node/rpc.cpp Outdated
boost::optional<std::string> modified_since_text (request.get_optional<std::string> ("modified_since"));
if (modified_since_text.is_initialized ())
auto count (decode_unsigned (request.get<std::string> ("count", ""), std::numeric_limits<uint64_t>::max ()));
if (!count)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here's the condition that should have a !ec if we're going with that style.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, good catch, I'll add that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

@PlasmaPower
Copy link
Contributor

That would be great. P0779R0 is a proposal to implement this, which will work with std::expected. Not sure what a good C++ idiom is right now.

Should we make our own macro for the time being?

@cryptocode
Copy link
Contributor Author

cryptocode commented May 16, 2018

The proposal requires language changes. A decent C-style macro solution would require statement expressions, which is not supported by MSVC. Unless someone has come up with an alternative trick that I'm not aware of. Do you have any ideas on how to solve this properly on C++14?

@PlasmaPower
Copy link
Contributor

Oh, I forgot that statements aren't expressions in C++ (unlike in Rust 😉). We could probably make a macro that takes the name of a variable and sets the value, but that doesn't seem like a good idea.

@cryptocode cryptocode force-pushed the std-expected-rpc branch 2 times, most recently from 2c2825e to 9f8e10b Compare May 16, 2018 21:22
@cryptocode
Copy link
Contributor Author

I need std::expected/std::error_code in the IPC/API code now so I'll introduce it there. The RPC changes can be rebased on top of that later.

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.

2 participants