Loading...
Searching...
No Matches
The TfError Error Posting System

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:

enum MatrixCodes { MatrixHasBadDimensions, MatrixIsSingular };
TF_ADD_ENUM_NAME(MatrixHasBadDimensions, "Matrix has bad dimensions.");
TF_ADD_ENUM_NAME(MatrixIsSingular, "Matrix is singular");
};
SquareMatrix operator+ (const SquareMatrix& a, const SquareMatrix& b)
{
if (a.GetSize() != b.GetSize()) {
TF_ERROR(MatrixHasBadDimensions,
"a size: %d, b size: %d", a.GetSize(), b.GetSize());
return SquareMatrix::Zero();
}
// compute sum, return result.
}
void ErrorSavvyFunction()
{
SquareMatrix a = ... , b = ...;
TfErrorMark m; // Track errors issued from this point.
// Attempt an operation which might issue a TfError.
SquareMatrix c = a + b;
if (!m.IsClean()) {
// Some errors occurred -- look for the MatrixHasBadDimensions error.
for (TfError const &err: m) {
if (err.GetErrorCode() == MatrixHasBadDimensions) {
// Report or handle appropriately for this caller ...
}
}
// Do not propagate any errors to further callers.
m.Clear();
}
// The TF_HAS_ERRORS convenience macro calls TfErrorMark::SetMark() to begin
// tracking new errors, invokes the passed expression, then evaluates to
// true if any errors were issued during the expression evaluation,
// otherwise false. For example:
if (TF_HAS_ERRORS(m, c = 2*a + 3*b)) {
for (TfError const &err: m) {
// Handle/report error...
}
}
}
An enum class that records both enum type and enum value.
Definition: enum.h:137
Represents an object that contains error information.
Definition: error.h:49
Class used to record the end of the error-list.
Definition: errorMark.h:65
bool Clear() const
Remove all errors in this mark from the error system.
Definition: errorMark.h:109
bool IsClean() const
Return true if no new errors were posted in this thread since the last call to SetMark(),...
Definition: errorMark.h:99
#define TF_ERROR(...)
Issue an internal programming error, but continue execution.
Definition: diagnostic.h:71
#define TF_ADD_ENUM_NAME(VAL,...)
Macro used to associate a name with an enumerated value.
Definition: enum.h:470
#define TF_HAS_ERRORS(marker, expr)
Convenience macro to check if errors occurred.
Definition: errorMark.h:203
#define TF_REGISTRY_FUNCTION(KEY_TYPE)
Define a function that is called on demand by TfRegistryManager.

Usage

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:

// The TF_ERROR macro can accept a message in several forms:
// string literal message:
TF_ERROR(NoSuchFile, "File not found");
// std::string message:
std::string msg = "File not found: ";
TF_ERROR(NoSuchFile, msg + fileName);
// printf-style message:
TF_ERROR(NoSuchFile, "File not found: %s", fileName.c_str());

Additionally, an error may have an arbitrary piece of contextual data stored with it as follows:

// The same three message options as above, but with an additional extra object
// of any type to convey useful contextual information to error handlers.
TF_ERROR(NoSuchFile, filePathConfig,
<literal, std::string, or printf-style message>);

Callers inspecting TfError objects can retrieve this extra object by calling TfError::GetInfo() (really a base-class member function TfDiagnosticBase::GetInfo()).

for (TfError const &err: m) {
if (m.GetCode() == NoSuchFile) {
if (const FilePathConfig *pathConfig = err.GetInfo<FilePathConfig>()) {
// Report error with contextual information from pathConfig.
}
}
}

TfError::GetInfo() returns nullptr if there is no extra data or if it's not holding (exactly) the given type.

Built-in Error Reporting

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.

Enum Conventions

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.

struct OofError {
enum Type {
OofErrorCode1,
OofErrorCode2,
OofErrorCode3
};
};

Register the enum values with the TfEnum registry to use them with TfError. For example, in oof/error.cpp:

TF_ADD_ENUM_NAME(OofError::OofErrorCode1, "Description of error code1");
TF_ADD_ENUM_NAME(OofError::OofErrorCode2, "Description of error code2");
TF_ADD_ENUM_NAME(OofError::OofErrorCode3, "Description of error code3");
};

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.

// wrapOofError.cpp
#include <boost/python/class.hpp>
#include <boost/python/scope.hpp>
using namespace boost::python;
void wrapOofError() {
typedef OofError This;
// Wrap the enum under a scope called Error. The enum values
// will be available in python under Oof.Error.*.
scope errorTypeScope = class_ <This>("Error", no_init);
}
Provide facilities for wrapping enums for script.
Used to wrap enum types for script.
Definition: pyEnum.h:380

To make the enum values available directly under the module Oof, omit the errorTypeScope.

// wrapOofError.cpp
void wrapOofError() {
}

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.