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

core-clp: Add templates to support systematic and type-safe error code handling. #486

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

LinZhihao-723
Copy link
Member

Description

Problem Statement

In the current CLP core, we use a generic enum defined in clp/ErrorCode.hpp as our error code machism. We've been seeing problems of this design from the following perspectives:

  • These error codes are generic, they don't provide adequate context about what exactly the error is about. They are used across multiple different components inside our codebase without detailed explanations. For example, ErrorCode_Unsupported is used in both reader interfaces to indicate unsupported operations, and it is also used in WildcardToken to report an unsupported wildcard token. This error code itself doesn't provide the upper caller with the context of the error.
  • Different subcomponents may need their own error code enums. For example, the ffi/ir_stream component defines its own error enums IRErrorCode and IRProtocalErrorCode, instead of using the generic ErrorCode_xxx. There is no systematic way to organize these different error code enums across the code base.
  • With the current design, we cannot integrate our error code into other error handling tools, such as std_result from outcome, or std::expected in the future if we migrate to C++23.

Solution

In modern C++, the standard does provide a systematic and type-safe way of managing error codes: <system_error>. A detailed tutorial can be found in a cppcon talk here.
The idea of this standard is simple. As module developers, you need to extend std::error_code to be compatible with a customized std::error_category and an enum of possible errors. The std::error_category will be used to identify the context of the errors with human-readable messages for each defined error. And for module users, they only need to compare the enum against the std::error_code instance to inspect errors.
Furthermore, the standard library also provide capabilities to define mappings from multiple error codes to one "error condition" using std::error_condition, providing better management for sources of errors.
By using this library, we should be able to solve all the problems mentioned in the previous section.

Implementation

This PR implements ErrorCode as a generic error code template, which integrates std::error_code and can be extended to customized error enums.
To implement a new set of error codes, developers need to do the following steps:

  1. Define a strongly typed enum of all possible errors, for example, MyErrorCodeEnum
  2. Implement the specifications of the following methods from ErrorCodeCategory<MyErrorCodeEnum>:
    • name(): Defines the name of the error category
    • message(MyErrorCodeEnum error_enum): Provides a translation from the error enum to an error message string
  3. In std namespace, specialize the following template to be true:
template <>
struct is_error_code_enum<ErrorCode<MyErrorCodeEnum>> : std::true_type {};

After these three steps, ErrorCode<MyErrorCodeEnum> will be considered as a valid clp error code class. An instance of this class, initialized by an enum value from MyErrorCodeEnum, will be considered as a domain-specific error. This error can be implicitly converted into a std::error_code, providing compatibilities with outcome::std_result and user-level error inspections.
In addition to the template implementation, we also include a unit test to not only test the basic usage of the ErrorCode template, but also give an example of how to use the template to define a set of errors of a specific domain.
Last but not least, the current implementation should be extendable to add error condition supports in the future.

Validation performed

  1. Ensure all GitHub workflows passed.
  2. Add unit tests to cover the basic usage of the template.
  3. Tested with PR Add initial implementation of regex_utils library containing a regex to wildcard string translator and a corresponding std::error_code enum and category. #482 to ensure this PR is compatible with the std::error_code handling introduced there.

Copy link
Member

@davidlion davidlion left a comment

Choose a reason for hiding this comment

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

Lets resolve some of the inconsistent naming:

  • we switch between err and error throughout the code
  • errno in get_errno and error_num in other places should be the same as far as I can tell
  • m_error is a little ambiguous given almost everything is prefixed with "error" and its getter (currently get_err_enum) should match the name

I think it would be good if we add a test where we make a condition with a stdlib error_code and an error_code from your template to sanity check / ensure we don't break any functionality.

@LinZhihao-723
Copy link
Member Author

Made some of the methods inline to the template declaration so that they can't be specialized

Copy link
Contributor

@Bill-hbrhbr Bill-hbrhbr left a comment

Choose a reason for hiding this comment

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

The major thing I'm concerned with is how to actually use this error-handling library beyond the examples provided in the unit tests.
From what I've seen after testing out this library myself:

  1. The templates are implemented in the namespace clp::error_handling, so the overridden function implementations need to be in the same namespace as well. This is a bit counter-intuitive if you are working in a different namespace.
  2. Ideally the ErrorCode enum class and the actual ErrorCode class should be placed in another header file so that the same error code system can be potentially used across different files. They could also be declared in a namespace that' could be entirely orthogonal to clp::error_handling. I don't have a clear clue on how that can be achieved. In the unit tests, these classes are defined in a global scope, and not in a header file. So a README that can guide library users would be very helpful.
  3. Once the error code library works 100%, we can implement the same for the error conditions, and then figure out all possible scenarios of them comparing against each other.
  4. we've discussed things like using small/harmless macros to help library users implementing their custom error codes. But this is not as important as figuring out how to use it properly first.

@LinZhihao-723
Copy link
Member Author

The major thing I'm concerned with is how to actually use this error-handling library beyond the examples provided in the unit tests. From what I've seen after testing out this library myself:

  1. The templates are implemented in the namespace clp::error_handling, so the overridden function implementations need to be in the same namespace as well. This is a bit counter-intuitive if you are working in a different namespace.
  2. Ideally the ErrorCode enum class and the actual ErrorCode class should be placed in another header file so that the same error code system can be potentially used across different files. They could also be declared in a namespace that' could be entirely orthogonal to clp::error_handling. I don't have a clear clue on how that can be achieved. In the unit tests, these classes are defined in a global scope, and not in a header file. So a README that can guide library users would be very helpful.
  3. Once the error code library works 100%, we can implement the same for the error conditions, and then figure out all possible scenarios of them comparing against each other.
  4. we've discussed things like using small/harmless macros to help library users implementing their custom error codes. But this is not as important as figuring out how to use it properly first.
  1. I think this is just like all the other library templates. It is expected to set an alias with using MyErrorCode = clp::error_handling::ErrorCode<MyErrorType> in a user namespace. You can also add an alias to the error category, but that's not necessary
  2. In the header file, you only need: (1) error enums, (2) alias as discussed above, and (3) std specification making the ErrorCode to be compatible with std::error_code. The specification of name and message of error category should go into the implementation file (cpp). I'm not sure if a README is really necessary, since as long as we have real examples it's just some copy-paste with some renaming. Ideally with the unit tests and real use cases I will add to the clp core in the near future, it should be sufficient for people to figure out what to do.
  3. The current design can be extended with error conditions.
  4. I agree that the macro can wrap the std specification to make things more convenient

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.

3 participants