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

Add error code categories to Status #2200

Closed
mbautin opened this issue Sep 2, 2019 · 0 comments
Closed

Add error code categories to Status #2200

mbautin opened this issue Sep 2, 2019 · 0 comments
Assignees

Comments

@mbautin
Copy link
Contributor

mbautin commented Sep 2, 2019

We need a way for different parts of the codebase to augment instances of the Status class with its own types of error codes without adding those error codes to Status. Examples of such error codes include POSIX I/O error codes, YCQL errors, and YSQL errors. Status is used throughout the entire codebase, and simply adding more specific error codes there will introduce unnecessary dependencies between different parts of the codebase, not to mention forcing the recompilation of almost all of the code.

spolitov added a commit that referenced this issue Sep 5, 2019
Summary:
We need a way for different parts of the codebase to augment instances of the Status class with its own types of error codes without adding those error codes to Status.
Examples of such error codes include POSIX I/O error codes, YCQL errors, and YSQL errors.
Status is used throughout the entire codebase, and simply adding more specific error codes there would introduce unnecessary dependencies between different parts of the codebase, not to mention forcing the recompilation of almost all of the code.

This diff introduces the ability to add extra error codes to the status. A status can now contain a map of error code categories to error code values (at most one per category), which could be represented as simple enums, or have a more complex representation, including custom error messages, etc. This serialization format is part of an error category definition.

There is a hard limit of 255 on the number of possible error categories, because the category id is represented as a non-zero byte. Category ids and serialization format of individual error codes in each category are part of the wire protocol, so when changing these, backward compatibility and rolling upgrades should be considered. Error category ids should also be globally unique across the codebase, which is checked by a global error code category registry at server startup.

Inside of in-memory and on-the-wire representations of a `Status`, a category id is stored as one byte, followed by the category-specific binary representation (e.g. a little-endian number for number-based or "integral" error categories). Accessing the error code of a specific category takes O(N) time, where N is the number of different categories of error codes associated with a given `Status` object. In practice N is assumed to be very small and it obviously can't exceed 255. This representation of error codes is terminated with a special dummy category id of 0.

A component might define a simple custom error code that is backed by an enum as follows, e.g. in a header `pizza_error.h` (omitting namespace and other boilerplate):

```
YB_DEFINE_ENUM(PizzaCode, (kSoggy)(kUndercooked)(kTooManyBubbles)(kTooMuchSauce)(kDoesNotTasteGood));

struct PizzaErrorTag : yb::IntegralErrorTag<PizzaCode> {
  // This category id is part of the wire protocol and should not be changed once released.
  static constexpr uint8_t kCategory = 123;

  static const std::string& ToMessage(Value value);
};

typedef yb::StatusErrorCodeImpl<PizzaErrorTag> PizzaError;
```

(Note that the comment `// This category id is part of the wire protocol and should not be changed once released` should be there above the category code constant, by our convention.)

And in the corresponding file called e.g. `pizza_error.cc`

```
const std::string kPizzaMsgs[] = {
    "Soggy pizza",
    "Undercooked pizza",
    "Too many bubbles in the crust",
    "Too much sauce on the pizza",
    "Does not taste good"
};

static_assert(arraysize(kPizzaCode) == kElementsInPizzaCode,
              "Wrong number of pizza error messages");

} // namespace

const std::string& PizzaErrorTag::ToMessage(Value value) {
  return kPizzaMsgs[yb::to_underlying(value)];
}

static const std::string kPizzaErrorCategoryName = "pizza";

static yb::StatusCategoryRegisterer pizza_error_category_registerer(
    yb::StatusCategoryDescription::Make<PizzaErrorTag>(&kPizzaErrorCategoryName));
```

Then, a status could be created with this error code:
```
return STATUS(IllegalState, "Something is wrong with the pizza", PizzaError(PizzaCode::kTooMuchSauce));
```
Also, an existing status object could be augmented with an error code:
```
return status.CloneAndAddErrorCode(PizzaError(PizzaCode::kDoesNotTasteGood));
```
(This would replace an existing error code of the same category if it already exists in the status).

Test Plan:
Jenkins

Backward compatibility was tested using the following steps:
1) In wire_protocol.cc the following line was commented: `pb->set_errors(error_codes.data(), error_codes.size());`
  So we check that errors are perfectly serialized and deserialized in an old format.
2) Jenkins launched for modified diff. And the test results are checked.
3) Code uncommented.

Reviewers: nicolas, mikhail

Reviewed By: mikhail

Subscribers: ybase, bogdan

Differential Revision: https://phabricator.dev.yugabyte.com/D6972
@spolitov spolitov closed this as completed Sep 6, 2019
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

No branches or pull requests

2 participants