lib: add thread-safe error reporting API
This patch adds an API for rich error reporting from an internal module
(library, Python plugin provider, etc.), from a component, from a
component class (query operation), from a message iterator, or from a
graph listener function.
The goal of this is, as a library user, to have access to an organized
list of causes leading to a specific error when a library function
fails. Those causes have module name and message properties, and can
occur within user code. This is similar to a stack trace carried by an
exception object in other languages.
The objective is that a program (the CLI, for example) can print a
report like this (module name between square brackets):
ERROR: Graph failed to run completely:
[ctf1 (stream0): src.ctf.fs] Cannot decode packet header for packet
#546 in file `/path/to/stream/file`:
invalid packet size (17 bits).
CAUSED:
[ctf1 (stream0): src.ctf.fs] Cannot get next message.
CAUSED:
[Babeltrace library] Message iterator's "next" method failed; port
`port-name` of component `ctf1` (`src.ctf.fs`).
CAUSED:
[mux (out): flt.utils.muxer] Cannot get next upstream message
iterator's message for input port `in3`.
CAUSED:
[Babeltrace library] Message iterator's "next" method failed; port
`out` of component `mux` (`flt.utils.muxer`).
CAUSED:
[txt: sink.text.pretty] Cannot get next upstream message iterator's
message.
CAUSED:
[Babeltrace library] Sink component's "consume" method failed.
CAUSED:
[Babeltrace library] Graph stopped running.
The new concepts are:
Error cause actor:
The actor which is responsible for the cause of an error. The
available actors are:
Unknown:
No specific actor (the library will use this, for example, as
well as graph listeners).
Component:
The user code of a component caused the error.
Component class:
The user code of a component class (during a query operation)
caused the error.
Message iterator:
The user code of a message iterator caused the error.
Error cause:
The cause of an error, that is, the context in which the error
occured. An error cause has an actor type, a module name, a message,
and, depending on its actor, other properties.
For example, a single error cause could contain a whole Python stack
trace as its message.
The API function names start with `bt_error_cause`.
Error:
A list of error causes.
The API function names start with `bt_error`.
Current thread:
Everything related to the current thread. The only available
property is its error object.
The API function names start with `bt_current_thread`.
The library header `error-const.h` contains what you need to borrow the
causes of an error object: bt_error_get_cause_count() returns the number
of contained error causes, and with bt_error_borrow_cause_by_index() you
can borrow a specific error cause.
When you call a library function and it returns an error status, you
MUST either:
* Handle the error, and then continue. In that case, you need to clear
the current thread's error object somehow.
If you don't care about the causes of the error, you can call
bt_current_thread_clear_error().
If you need to check the causes of the error, call
bt_current_thread_take_error() which transfers the ownership of the
current thread's error to you, clearing the thread's error at the same
time. This is similar, for example, to how PyErr_Fetch() retrieves the
error indicator and then clears it.
Once you're done with the error object you now own, you can either:
* Discard it with bt_error_release().
* Move it back to the current thread as its error object with
bt_current_thread_move_error().
* Propagate the error, that is, return an error status yourself.
Optionally, you can append an error cause (from your context) to the
current thread's error object with one of the
bt_current_thread_error_append_cause_from_*() functions or
BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_*() macros (more details
below).
In that case, the error will be handled as explained above by one of
your callers.
The error cause API, depending on the error cause actor, offers the
component name, component class name and type, plugin name (if
available), and component output port name.
I'm not comfortable keeping a weak reference on the component, component
class, plugin, or message iterator object within the error cause object
itself because the error could possibly outlive the component object.
I'm also not comfortable keeping a strong reference because I'm pretty
sure someone will forget to call bt_current_thread_clear_error() or
bt_error_release() eventually and in that case it could cause important
leaks. Leaking a few strings is not the end of the world, but if you
leak the whole graph, then some finalization methods won't be called and
you could have some serious incompleteness.
You cannot append error causes to any error object because this would
require taking the current thread's error object and then moving it back
each time you need to append an error cause.
Instead, you can append an error cause to the current thread's error
specifically with one of:
bt_current_thread_error_append_cause_status
bt_current_thread_error_append_cause_from_unknown(
const char *module_name, const char *file_name,
uint64_t line_no, const char *msg_fmt, ...);
bt_current_thread_error_append_cause_status
bt_current_thread_error_append_cause_from_component(
bt_self_component *self_comp, const char *file_name,
uint64_t line_no, const char *msg_fmt, ...);
bt_current_thread_error_append_cause_status
bt_current_thread_error_append_cause_from_component_class(
bt_self_component_class *self_comp_class, const char *file_name,
uint64_t line_no, const char *msg_fmt, ...);
bt_current_thread_error_append_cause_status
bt_current_thread_error_append_cause_from_message_iterator(
bt_self_message_iterator *self_iter, const char *file_name,
uint64_t line_no, const char *msg_fmt, ...);
#define BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_UNKNOWN(
_module_name, _msg_fmt, ...)
#define BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT(
_self_comp, _msg_fmt, ...)
#define BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT_CLASS(
_self_cc, _msg_fmt, ...)
#define BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_MESSAGE_ITERATOR(
_self_iter, _msg_fmt, ...)
The only available `bt_current_thread_error_append_cause_status` values
are:
* `BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_STATUS_OK`
* `BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_STATUS_MEMORY_ERROR`
The macro versions call the non-macro versions with `__FILE__` and
`__LINE__` for the `file_name` and `line_no` parameters.
The functions above work like printf() from their `msg_fmt` argument to
set the error cause's message.
For bt_current_thread_error_append_cause_from_unknown(), the module name
can be anything. It should clearly identify the actor (for example,
`Babeltrace library`, `Babeltrace CLI`, `Python bindings`). When the
error cause's actor is not unknown, then the module name is conveniently
set automatically:
Component:
`my-comp: src.ctf.fs`
Component class:
`src.ctf.fs` or `src.comp-cls` (no plugin)
Message iterator:
`my-comp (out2): src.ctf.fs`
As of this patch, the projet does not use the new API. This work is
reserved for subsequent patches.
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: Ia791b2ff9bbf7a91e7c37416ba748c1186ee69a4
Reviewed-on: https://review.lttng.org/c/babeltrace/+/1474
Tested-by: jenkins <jenkins@lttng.org>
12 files changed:
This page took 0.026677 seconds and 4 git commands to generate.