Flow 1.0.0
Flow project: Public API.
Classes | Namespaces | Macros | Functions
error.hpp File Reference

Classes

class  flow::error::Runtime_error
 An std::runtime_error (which is an std::exception) that stores an Error_code. More...
 

Namespaces

namespace  flow
 Catch-all namespace for the Flow project: A collection of various production-quality modules written in modern C++17, originally by ygoldfel.
 
namespace  flow::error
 Flow module that facilitates working with error codes and exceptions; essentially comprised of niceties on top boost.system's error facility.
 

Macros

#define FLOW_ERROR_EMIT_ERROR(ARG_val)
 Sets *err_code to ARG_val and logs a warning about the error using FLOW_LOG_WARNING(). More...
 
#define FLOW_ERROR_EMIT_ERROR_LOG_INFO(ARG_val)
 Identical to FLOW_ERROR_EMIT_ERROR(), but the message logged has flow::log::Sev::S_INFO severity instead of S_WARNING. More...
 
#define FLOW_ERROR_LOG_ERROR(ARG_val)
 Logs a warning about the given error code using FLOW_LOG_WARNING(). More...
 
#define FLOW_ERROR_SYS_ERROR_LOG_WARNING()    FLOW_LOG_WARNING("System error occurred: [" << sys_err_code << "] [" << sys_err_code.message() << "].")
 Logs a warning about the (often errno-based or from a library) error code in sys_err_code. More...
 
#define FLOW_ERROR_SYS_ERROR_LOG_FATAL()    FLOW_LOG_FATAL("System error occurred: [" << sys_err_code << "] [" << sys_err_code.message() << "].")
 Logs a log::Sev::S_FATAL message about the (often errno-based or from a library) error code in sys_err_code, usually just before aborting the process or otherwise entering undefined-behavior land such as via assert(false). More...
 
#define FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(ARG_ret_type, ARG_method_name, ...)    FLOW_ERROR_EXEC_FUNC_AND_THROW_ON_ERROR(ARG_ret_type, ARG_method_name, this, __VA_ARGS__)
 Narrow-use macro that implements the error code/exception semantics expected of most public-facing Flow (and Flow-inspired) class method APIs. More...
 
#define FLOW_ERROR_EXEC_FUNC_AND_THROW_ON_ERROR(ARG_ret_type, ARG_function_name, ...)
 Identical to FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() but supports not only class methods but free functions as well. More...
 

Functions

template<typename Func , typename Ret >
bool flow::error::exec_and_throw_on_error (const Func &func, Ret *ret, Error_code *err_code, util::String_view context)
 Helper for FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() macro that does everything in the latter not needing a preprocessor. More...
 
template<typename Func >
bool flow::error::exec_void_and_throw_on_error (const Func &func, Error_code *err_code, util::String_view context)
 Equivalent of exec_and_throw_on_error() for operations with void return type. More...
 

Macro Definition Documentation

◆ FLOW_ERROR_EMIT_ERROR

#define FLOW_ERROR_EMIT_ERROR (   ARG_val)
Value:
( \
::flow::Error_code FLOW_ERROR_EMIT_ERR_val(ARG_val); \
FLOW_LOG_WARNING("Error code emitted: [" << FLOW_ERROR_EMIT_ERR_val << "] " \
"[" << FLOW_ERROR_EMIT_ERR_val.message() << "]."); \
*err_code = FLOW_ERROR_EMIT_ERR_val; \
)
boost::system::error_code Error_code
Short-hand for a boost.system error code (which basically encapsulates an integer/enum error code and...
Definition: common.hpp:502
#define FLOW_UTIL_SEMICOLON_SAFE(ARG_func_macro_definition)
Use this to create a semicolon-safe version of a "void" functional macro definition consisting of at ...
Definition: util_fwd.hpp:1079

Sets *err_code to ARG_val and logs a warning about the error using FLOW_LOG_WARNING().

An err_code variable of type that is pointer to flow::Error_code must be declared at the point where the macro is invoked.

Parameters
ARG_valValue convertible to flow::Error_code. Reminder: Error_code is trivially/implicitly convertible from any error code set (such as errnos and boost.asio network error code set) that has been boost.system-enabled.

◆ FLOW_ERROR_EMIT_ERROR_LOG_INFO

#define FLOW_ERROR_EMIT_ERROR_LOG_INFO (   ARG_val)
Value:
( \
::flow::Error_code FLOW_ERROR_EMIT_ERR_LOG_val(ARG_val); \
FLOW_LOG_INFO("Error code emitted: [" << FLOW_ERROR_EMIT_ERR_LOG_val << "] " \
"[" << FLOW_ERROR_EMIT_ERR_LOG_val.message() << "]."); \
*err_code = FLOW_ERROR_EMIT_ERR_LOG_val; \
)

Identical to FLOW_ERROR_EMIT_ERROR(), but the message logged has flow::log::Sev::S_INFO severity instead of S_WARNING.

Parameters
ARG_valSee FLOW_ERROR_EMIT_ERROR().

◆ FLOW_ERROR_EXEC_AND_THROW_ON_ERROR

#define FLOW_ERROR_EXEC_AND_THROW_ON_ERROR (   ARG_ret_type,
  ARG_method_name,
  ... 
)     FLOW_ERROR_EXEC_FUNC_AND_THROW_ON_ERROR(ARG_ret_type, ARG_method_name, this, __VA_ARGS__)

Narrow-use macro that implements the error code/exception semantics expected of most public-facing Flow (and Flow-inspired) class method APIs.

The semantics it helps implement are explained in flow::Error_code doc header. More formally, here is how to use it:

First, please read flow::Error_code doc header. Next read on:

Suppose you have an API f() in some class C that returns type T and promises, in its doc header, to implement the error reporting semantics listed in the aforementioned flow::Error_code doc header. That is, if user passes in null err_code, error would cause Run_time error(e_c) to be thrown; if non-null, then *err_code = e_c would be set sans exception – e_c being the error code explaining what went wrong, such as flow::net_flow::error::Code::S_WAIT_INTERRUPTED. Then here's how to use the macro:

// Example API. In this one, there's a 4th argument that happens to follow the standard Error_code* one.
// arg2 is a (const) reference, as opposed to a pointer or scalar, and is most efficiently handled by adding
// cref() to avoid copying it.
T f(AT1 arg1, const AT2& arg2, Error_code* err_code = 0, AT3 arg3 = 0)
{
FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(T, // Provide the return type of the API.
// Forward all the args into the macro, but replace `err_code` => `_1`.
// Add [c]ref() around any passed-by-reference args.
arg1, cref(arg2), _1, arg3);
// ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
// ...Bulk of f() goes here! You can now set *err_code to anything without fear....
}
#define FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(ARG_ret_type, ARG_method_name,...)
Narrow-use macro that implements the error code/exception semantics expected of most public-facing Fl...
Definition: error.hpp:357

A caveat noted in the example above is handling references. If the argument is a const reference, then to avoid copying it during the intermediate call, surround it with cref(). We don't tend to use non-const references (preferring pointers for stylistic reasons), but if you must do so, then use ref(). All other types are OK to copy (by definition – since you are passing them by value a/k/a copy in the first place in the original signature).

See also
flow::Error_code for typical error reporting semantics this macro helps implement.
exec_void_and_throw_on_error() which you can use directly when ARG_ret_type would be void. The present macro does not work in that case; but at that point using the function directly is concise enough.
Parameters
ARG_ret_typeThe return type of the invoking method. In practice this would be, for example, size_t when wrapping a receive() (which returns # of bytes received or 0).
ARG_method_nameClass-qualified name of the invoking method. For example, flow::Node::listen(). Don't forget template parameter values, if applicable; e.g., note the <...> part of Peer_socket::sync_send<Const_buffer_sequence>.
...The remaining arguments, of which there must be at least 1, are to simply forward the arguments into the invoking method; thus similar to what one provided when calling the method that must implement the above-discussed error reporting semantics. However, the (mandatory, in this context) Error_code* err_code parameter MUST be replaced by the following identifier: _1 (as for bind()).

◆ FLOW_ERROR_EXEC_FUNC_AND_THROW_ON_ERROR

#define FLOW_ERROR_EXEC_FUNC_AND_THROW_ON_ERROR (   ARG_ret_type,
  ARG_function_name,
  ... 
)
Value:
( \
using namespace boost::placeholders; /* So they can use _1, _2, etc., without qualification. */ \
/* We need both the result of the operation (if applicable) and whether it actually ran. */ \
/* So we must introduce this local variable (note it's within a { block } so should minimally interfere with */ \
/* the invoker's code). We can't use a sentinel value to combine the two, since the operation's result may */ \
/* require ARG_ret_type's entire range. */ \
ARG_ret_type result; \
/* We provide the function: f(Error_code*), where f(e_c) == this->ARG_method_name(..., e_c, ...). */ \
/* Also supply context info of this macro's invocation spot. */ \
/* Note that, if f() is executed, it may throw Runtime_error which is the point of its existence. */ \
if (::flow::error::exec_and_throw_on_error(::flow::util::bind_ns::bind(&ARG_function_name, __VA_ARGS__), \
&result, err_code, \
/* See discussion below. */ \
FLOW_UTIL_WHERE_AM_I_LITERAL(ARG_function_name))) \
{ \
/* Aforementioned f() WAS executed; did NOT throw (no error); and return value was placed into `result`. */ \
return result; \
} \
/* f() did not run, because err_code is non-null. So no macro invoker should do its thing assuming that fact. */ \
/* Recall that the idea is that f() is just recursively calling the method invoking this macro with the same */ \
/* arguments except for the Error_code* arg. */ \
)
bool exec_and_throw_on_error(const Func &func, Ret *ret, Error_code *err_code, util::String_view context)
Helper for FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() macro that does everything in the latter not needing ...
Definition: error.hpp:128

Identical to FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() but supports not only class methods but free functions as well.

The latter macro is, really, a bit of syntactic sugar on top of the present macro, so that explicitly listing this as an arg can be omitted in the typical (but not universal) case wherein the API is a class method.

All usage notes for FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() apply here within reason.

Implementation notes

This macro exists purely to shorten boiler-plate. Furthermore, we don't like macros (debugger-unfriendly, hard to edit, etc.), so most of it is implemented in a function this macro invokes. However, a function cannot force its caller to return (which we need), so we need the preprocessor at least for that. We also use it to provide halfway useful source code context (file, line #, etc.) info. (It's not THAT useful – it basically identifies the throwing method; not where the method failed; but that's still pretty good and better than what one would achieve without using a macro.)

Another way to have done this would've been to split up T f() into T f() and T f_impl(); where the latter assumes non-null err_code; while the former calls it and throws flow::error::Runtime_error() if appropriate. That would be less tricky to de-boiler-plate, probably. However, the final code would be longer, and then both functions have to be documented, which is still more boiler-plate. In fact, at that point we might as well have two distinct APIs, as boost.asio does. The way it's done here may be a little trickier, but it's more concise (though, admittedly, a tiny bit slower with one redundant code path that could otherwise be avoided).

The implementation uses bind(), and a requirement on the ... macro args is to provide a bind()-compatible _1 argument. Generally, in this code base, we prefer lambdas over bind(). In this case, would it be possible to move to a lambda as well? How would the user supply _1, without bind()? I am not making this a formal to-do for now, as the existing system already appears slick and not trivially replaceable with lambdas.

See also
FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(), particularly all notes on how to use it, as they apply here similarly.
Parameters
ARG_ret_typeThe return type of the invoking method. In practice this would be, for example, size_t when wrapping a receive() (which returns # of bytes received or 0).
ARG_function_nameClass-qualified (if applicable) name of the invoking method. For example, flow::Node::listen(). Don't forget template parameter values, if applicable; e.g., note the <...> part of Peer_socket::sync_send<Const_buffer_sequence>.
...The remaining arguments, of which there must be at least 1, are to simply forward the arguments into the invoking method; thus similar to what one provided when calling the method that must implement the above-discussed error reporting semantics. However, the (mandatory, in this context) Error_code* err_code parameter MUST be replaced by the following identifier: _1 (as for bind()). If ARG_function_name is a class method, then the first arg to ... must be this. However in that case it is more concise and typical to use FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() instead.

◆ FLOW_ERROR_LOG_ERROR

#define FLOW_ERROR_LOG_ERROR (   ARG_val)
Value:
( \
::flow::Error_code FLOW_ERROR_LOG_ERR_val(ARG_val); \
FLOW_LOG_WARNING("Error occurred: [" << FLOW_ERROR_LOG_ERR_val << "] " \
"[" << FLOW_ERROR_LOG_ERR_val.message() << "]."); \
)

Logs a warning about the given error code using FLOW_LOG_WARNING().

Parameters
ARG_valSee FLOW_ERROR_EMIT_ERROR().

◆ FLOW_ERROR_SYS_ERROR_LOG_FATAL

#define FLOW_ERROR_SYS_ERROR_LOG_FATAL ( )     FLOW_LOG_FATAL("System error occurred: [" << sys_err_code << "] [" << sys_err_code.message() << "].")

Logs a log::Sev::S_FATAL message about the (often errno-based or from a library) error code in sys_err_code, usually just before aborting the process or otherwise entering undefined-behavior land such as via assert(false).

sys_err_code must be an object of type flow::Error_code in the context of the macro's invocation.

This is identical to FLOW_ERROR_SYS_ERROR_LOG_WARNING(), except the message logged has FATAL severity instead of a mere WARNING. Notes in that macro's doc header generally apply. However the use case is different.

The recommended (but in no way enforced or mandatory) pattern is something like:

Error_code sys_err_code;
// ...
// sys_err_code has been set to truthy value that is completely unexpected or so unpalatable as to make
// any attempt at recovery not worth the effort. Decision has therefore been made to abort and/or
// enter undefined-behavior land.
FLOW_LOG_FATAL("(...Explain what went wrong, and why it is very shocking; output values of interest; but not "
"`sys_err_code` since....) Details follow.");
// Enter undefined-behavior land.
// Different orgs/projects do different things; but a decent approach might be:
assert(false && "(...Re-explain what went wrong, so it shows up in the assert-trip message on some stderr.");
// Possibly really abort program, even if assert()s are disabled via NDEBUG.
std::abort();
#define FLOW_ERROR_SYS_ERROR_LOG_FATAL()
Logs a log::Sev::S_FATAL message about the (often errno-based or from a library) error code in sys_er...
Definition: error.hpp:302
#define FLOW_LOG_FATAL(ARG_stream_fragment)
Logs a FATAL message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:167

◆ FLOW_ERROR_SYS_ERROR_LOG_WARNING

#define FLOW_ERROR_SYS_ERROR_LOG_WARNING ( )     FLOW_LOG_WARNING("System error occurred: [" << sys_err_code << "] [" << sys_err_code.message() << "].")

Logs a warning about the (often errno-based or from a library) error code in sys_err_code.

sys_err_code must be an object of type flow::Error_code in the context of the macro's invocation. See also FLOW_ERROR_SYS_ERROR_LOG_FATAL().

Note this implies a convention wherein system (especially errno-based or from a libary) error codes are to be saved into a stack variable or parameter flow::Error_code sys_err_code. Of course if you don't like this convention and/or this error message, it is trivial to log something manually.

It's a functional macro despite taking no arguments to convey that it mimics a void free function.

The recommended (but in no way enforced or mandatory) pattern is something like:

Error_code sys_err_code;
// ...
// sys_err_code has been set to truthy value. Decision has therefore been made to log about it but otherwise
// recover and continue algorithm.
FLOW_LOG_WARNING("(...Explain what went wrong; output values of interest; but not `sys_err_code` since....) "
"Details follow.");
// Continue operating. No assert(false); no std::abort()....
#define FLOW_ERROR_SYS_ERROR_LOG_WARNING()
Logs a warning about the (often errno-based or from a library) error code in sys_err_code.
Definition: error.hpp:269
#define FLOW_LOG_WARNING(ARG_stream_fragment)
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_compone...
Definition: log.hpp:152