Error Handling¶
dftracer utilities uses a typed error model built around a single error category enum, a value type for recoverable failures, and an exception hierarchy for unrecoverable ones. Both the C++ API and the Python bindings expose the same categories, so a failure raised deep in the library surfaces as a specific, catchable error.
Everything below lives in dftracer::utils and is declared in
<dftracer/utils/core/common/error.h>.
ErrorCode¶
ErrorCode is the coarse failure category attached to every error:
Code |
Meaning |
|---|---|
|
Unclassified failure. |
|
Broken invariant or library bug. |
|
Bad caller input. |
|
Missing file, key, or entity. |
|
Filesystem or I/O failure. |
|
Parse or decode failure (JSON, trace format, …). |
|
(De)compression failure or corrupt compressed data. |
|
Query DSL error. |
|
Reader subsystem failure. |
|
Indexer subsystem failure. |
|
Pipeline or executor failure. |
|
Aggregation subsystem failure. |
error_code_name(ErrorCode) returns the code’s name as a const char*.
DFTUtilsError and Result<T>¶
DFTUtilsError is the error carried as a value. Use it with Result<T>
for recoverable failures, reserving exceptions for the unrecoverable:
struct DFTUtilsError {
ErrorCode code = ErrorCode::UNKNOWN;
std::string message;
std::string format() const; // "<CODE>: <message>"
};
template <typename T>
using Result = expected<T, DFTUtilsError>; // tl::expected
Construct the error channel with make_error:
Result<std::size_t> parse_count(std::string_view s) {
if (s.empty()) {
return make_error(ErrorCode::INVALID_ARGUMENT, "empty input");
}
return s.size();
}
auto r = parse_count(text);
if (!r) {
LOG_ERROR("%s", r.error().format().c_str());
} else {
use(*r);
}
DFT_TRY¶
DFT_TRY is a propagation helper for coroutines whose return type is
Result<T>. Used as a statement, it evaluates a Result-returning
expression; on failure it co_returns the moved-out error, otherwise it
binds the unwrapped moved-out success to the given declaration. This removes
the manual check-and-return boilerplate when chaining fallible steps:
coro::CoroTask<Result<std::size_t>> total_size() {
DFT_TRY(auto a, co_await read_one());
DFT_TRY(auto b, co_await read_two());
co_return a.size() + b.size();
}
If read_one() fails, its error is returned immediately and read_two()
is never called.
DFTUtilsException¶
DFTUtilsException derives std::runtime_error and carries an
ErrorCode retrievable via code(). It is the base of every domain
exception, so a single catch can inspect the category:
try {
reader.read_lines(...);
} catch (const DFTUtilsException& e) {
if (e.code() == ErrorCode::NOT_FOUND) { /* ... */ }
LOG_ERROR("%s", e.what());
}
Two factories build the message without a manual std::string concat.
cat concatenates typed arguments (numbers go through to_chars, no
format string); fmt is printf-style:
throw DFTUtilsException::cat(ErrorCode::IO,
"Cannot open ", path, ": errno=", e);
throw DFTUtilsException::fmt(ErrorCode::IO,
"Cannot open %s: errno=%d", path.c_str(), e);
Subsystem exceptions¶
The subsystem exceptions derive DFTUtilsException and fix the
ErrorCode for their domain, so catch (const DFTUtilsException&) and
catch (const std::runtime_error&) both still work:
Class |
ErrorCode |
Header |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Each subsystem exception also carries a finer-grained Type enum for its
domain (for example ReaderError::FILE_IO_ERROR,
IndexerError::DATABASE_ERROR, PipelineError::TIMEOUT_ERROR),
retrievable via get_type() (IndexerError uses type()). The
ErrorCode from code() stays fixed for the subsystem; the Type
narrows the cause within it.
rethrow_and_clear¶
For coroutine plumbing that stores a captured failure as a
std::exception_ptr, rethrow_and_clear (in
<dftracer/utils/core/common/exception_helpers.h>) moves the stored
exception out, clears the slot, then rethrows. Clearing first prevents
rethrowing the same exception twice if the holder is awaited again:
std::exception_ptr pending;
// ...
if (pending) {
dftracer::utils::rethrow_and_clear(pending); // [[noreturn]]
}
Python¶
The Python bindings raise the matching typed exception, mapped from the C++
ErrorCode. Every one derives DFTUtilsError, which in turn derives the
built-in RuntimeError (so existing except RuntimeError code keeps
working). See Quick Start Guide for a usage example.
Python exception |
Raised for ErrorCode |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|