Skip to content

kernel: Handle fatal errors through return values

Based on #25665.

Currently functions issuing fatal errors call the fatalError notification method to issue a shutdown procedure. This makes it hard for higher level functions to figure out if an error deeper in the call stack occurred. For users of the kernel it might also be difficult to assert which function call issued a fatal error if they are being run concurrently. If the kernel would eventually be used by external users, getting fatal error information through a callback instead of function return values would also be cumbersome and limiting. Unit, bench, and fuzz tests currently don't have a way to effectively test against fatal errors.

This patch set is an attempt to make fatal error handling in the kernel code more transparent. Fatal errors are now passed up the call stack through util::Result<T, FatalError> failure values. A previous attempt at this by theuni always immediately returned failure values if a function call returned a failure. However, this is not always desirable (see discussion here). Sometimes, further operations can still be completed even if a fatal error was issued. The solution to this is that these "ignored" errors are still moved through util::Result's error string values with its .MoveMessages method, even while a failure value in the result is not present.

Next to some smaller behavior changes, the most significant change is that the issuing of a shutdown procedure is delayed until a potential fatal error is handled as opposed to immediately when it is first encountered. Another effect is that potential fatal errors are now asserted in the bench, fuzz and unit tests. Some of the currently not immediately returned fatal errors need some further scrutiny. These are marked with a TODO (fatal error) comment and could be tackled in a later PR.

To validate this approach a new clang-tidy check is introduced. It implements the following checks:

  1. If a function calls another function with a util::Result<T, FatalCondition> return type, its return type has to be util::Result<T, FatalCondition> too, or it has to handle the value returned by the function with one of CheckFatal, HandleFatalError, UnwrapFatalError, or CheckFatalFailure.
  2. In functions returning a util::Result<T, FatalCondition> a call to a function returning a util::Result<T, FatalCondition> needs to propagate the value by either:
    • Returning it immediately
    • Assigning it immediately to an existing result with .MoveMessages() or .Set()
    • Eventually passing it as an argument to a .MoveMessages()

This PR is part of the libbitcoinkernel project and is a step towards stage 2, creating a more refined kernel API.

Merge request reports

Loading