![]() |
|
The TfError system is a developer-level mechanism used to indicate that a non-fatal error has occurred. The primary benefit of this facility is that it enables separating the concerns of error detection from error handling/reporting without breaking the normal flow of control, in contrast to exception propagation.
A good example of where this kind of separation is desirable is in "cross-interface" code situations. These situations occur when a called library routine detects an error condition, but the action to take in response is unclear; different callers want to report or log the error differently. With TfError, the library can record that the error occurred, but leave the handling and reporting to callers as they see fit.
Use of TfError is not meant to discourage functions from reporting errors by return value or by extra parameter passing. Instead, the TfError facility is a supplement for situations where error return codes or extra parameters are cumbersome or problematic.
Post a TfError using the TF_ERROR() macro. Since normal flow of control is maintained, the posting code must return gracefully to its caller.
Each thread maintains its own separate error list. This means that by default, errors reported in a thread can only be observed within that thread, but the TfErrorTransport class provides a mechanism to transport errors from one thread to another. This is particularly useful in scoped parent-child parallel contexts.
Use the TfErrorMark object to detect errors posted by called code. If errors have occurred, TfErrorMark::IsClean() returns false, and you can iterate the contained errors using a range-for loop, or begin()/end() iterators.
The following code sample illustrates typical use of TfError:
The TF_ERROR macro is always invoked with a diagnostic enum code. See Enum Conventions for an example of registering an enum type and its values as diagnostic codes. The calling patterns for TF_ERROR() are shown in the following examples:
Additionally, an error may have an arbitrary piece of contextual data stored with it as follows:
Callers inspecting TfError objects can retrieve this extra object by calling TfError::GetInfo() (really a base-class member function TfDiagnosticBase::GetInfo()).
TfError::GetInfo() returns nullptr
if there is no extra data or if it's not holding (exactly) the given type.
If there are no TfErrorMark objects in the current thread, TF_ERROR() will immediately report the error, either to the currently installed TfDiagnosticMgr::Delegate objects, or to stderr if there are no delegates.
If there are TfErrorMark objects in the current thread, TF_ERROR() will instead append the error to the thread's local error list to be potentially handled later by a caller. It also appends its message to an internal crash log text buffer. When the last TfErrorMark object on a thread is destroyed, if the error list is not empty, then those remaining errors are reported to either delegates or stderr, as above, and its text is removed from the crash log buffer.
In case of an unrecoverable crash, Tf's crash handler will emit all the pending, unreported diagnostic error messages to the crash report. This way, diagnostic messages for unhandled errors are always reported, either via delegates/stderr, or to the crash log in case of a crash.
If you have several different error conditions to distinguish, create an enum with multiple values describing the various conditions. In a library named Oof, the enum (or enums, should you group error states into different categories) could be declared in a header file named oof/error.h.
Register the enum values with the TfEnum registry to use them with TfError. For example, in oof/error.cpp:
Also wrap the enum to python in oof/wrapError.cpp.
The following code allows the enum values to be accessible from python under a scope named Error.
To make the enum values available directly under the module Oof, omit the errorTypeScope.
Even if you do not wish to distinguish between error states within a library, the fact that these errors came from the same module of code is in itself useful information. In this case, create an enum with a single value, and pass that value.
On the other hand, while proivding a unique error enumerant for every error condition gives the most specificity, it can also be tedious and cumbersome for both error producers and consumers. Think carefully about the main categories of errors your library generates. The best designs generally have a small number of error enumerants, instead adding nuance and subtlety in the error messages or diagnostic info objects.
Remember to document the errors that your public API might issue, and under what circumstances it does so. This makes it easier for callers to detect and handle the errors gracefully.
For convenience Tf supplies two general macros that issue TfErrors with Tf-supplied error enum values: TF_CODING_ERROR() and TF_RUNTIME_ERROR(). These macros issue TfErrors with the error enum values TF_DIAGNOSTIC_CODING_ERROR_TYPE and TF_DIAGNOSTIC_RUNTIME_ERROR_TYPE, respectively. Use TF_CODING_ERROR() where there is a demonstrable software error; for example, if passed arguments are invalid, or for an unmet precondition. Use TF_RUNTIME_ERROR() for errors that are not due to software bugs; for example, corrupt or invalid data in a file, disk space exhausted, network server unresponsive.