lib: add thread-safe error reporting API
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Sat, 15 Jun 2019 20:48:07 +0000 (16:48 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Sat, 6 Jul 2019 03:47:50 +0000 (23:47 -0400)
commit553c4bab3cad8ad569c14a01b39a66a2d8bcde7c
treec1f2eabdd6350be9157603c872778b0d2d6deb66
parent2ce06c9e12da671dd1098ed4174a070af95cb406
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:
CONTRIBUTING.adoc
include/Makefile.am
include/babeltrace2/babeltrace.h
include/babeltrace2/current-thread.h [new file with mode: 0644]
include/babeltrace2/error-cause-const.h [new file with mode: 0644]
include/babeltrace2/error-const.h [new file with mode: 0644]
include/babeltrace2/types.h
src/lib/Makefile.am
src/lib/current-thread.c [new file with mode: 0644]
src/lib/error.c [new file with mode: 0644]
src/lib/error.h [new file with mode: 0644]
src/lib/lib-logging.c
This page took 0.027419 seconds and 4 git commands to generate.