Flow 2.0.0
Flow project: Full implementation reference.
error.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include "flow/log/log.hpp"
24#include <boost/system/system_error.hpp>
25#include <stdexcept>
26
27namespace flow::error
28{
29// Types.
30
31/**
32 * An `std::runtime_error` (which is an `std::exception`) that stores an #Error_code. We derive from boost.system's
33 * `system_error` which already does that nicely. This polymorphic subclass merely improves the `what()`
34 * message a little bit -- specially handling the case when there is no #Error_code, or it is falsy -- but is
35 * otherwise identical.
36 *
37 * ### Rationale ###
38 * It is questionable whether Runtime_error is really necessary. One can equally well simply throw
39 * `boost::system::system_error(err_code, context)` when `bool(err_code) == true`, and
40 * `std::runtime_error(context)` otherwise. Indeed the user should feel 100% free to do that if desired.
41 * flow::error::Runtime_error is mere syntactic sugar for code brevity, when one indeed has an `err_code` that may
42 * or may not be falsy: they can just construct+throw a Runtime_error and not worry about the bifurcation.
43 * In the end one likely just catches `std::exception exc` and logs/prints `exc.what()`: how precisely it was thrown
44 * is of low import, typically. Runtime_error provides a concise way (with some arguable niceties like the use
45 * of `String_view`) to throw it when desired.
46 */
49{
50public:
51 // Constructors/destructor.
52
53 /**
54 * Constructs Runtime_error.
55 *
56 * @param err_code_or_success
57 * The #Error_code describing the error if available; or the success value (`Error_code()`)
58 * if an error code is unavailable or inapplicable to this error.
59 * In the latter case what() will omit anything to do with error codes and feature only `context`.
60 * @param context
61 * String describing the context, i.e., where/in what circumstances the error occurred/what happened.
62 * For example: "Peer_socket::receive()" or "Server_socket::accept() while checking
63 * state," or a string with line number and file info. Try to keep it brief but informative.
64 * FLOW_UTIL_WHERE_AM_I_STR() may be helpful.
65 */
66 explicit Runtime_error(const Error_code& err_code_or_success, util::String_view context = "");
67
68 /**
69 * Constructs Runtime_error, when one only has a context string and no applicable/known error code.
70 * Formally it's equivalent to `Runtime_error(Error_code(), context)`: it is syntactic sugar only.
71 *
72 * @param context
73 * See the other ctor.
74 */
75 explicit Runtime_error(util::String_view context);
76
77 // Methods.
78
79 /**
80 * Returns a message describing the exception. Namely:
81 * - If no/success #Error_code passed to ctor: Message includes `context` only.
82 * - If non-success #Error_code passed to ctor: Message includes: numeric value of `Error_code`, a brief
83 * string representing the code category to which it belongs, the system message corresponding to the
84 * `Error_code`, and `context`.
85 *
86 * @return See above.
87 */
88 const char* what() const noexcept override;
89
90private:
91 // Data.
92
93 /**
94 * This is a copy of `context` from ctor if `!err_code_or_success`; or unused otherwise.
95 *
96 * ### Rationale ###
97 * We want what() to act as follows:
98 * - `!err_code_or_success`: Error codes are inapplicable; return `context` only.
99 * - Else: Return a string with `context`; plus all details of the error code.
100 *
101 * The latter occurs in our superclass `what()` already, if we pass up `context` to the super-ctor.
102 * Now suppose `!err_code_or_success`. No matter which super-ctor we use, it will memorize an `Error_code` --
103 * if we pass `Error_code()` it'll remember that; if use a ctor that does not take an `Error_code()` it will
104 * memorize its own `Error_code()`. Therefore we must override `what()` behavior in our own what() in that case.
105 *
106 * Therefore this algorithm works:
107 * - `!err_code_or_success`: Memorize `context` in #m_context_if_no_code. what() just returns the latter.
108 * Do not pass up `context`: we shall never use superclass's `what()` which is the only thing that would
109 * access such a thing.
110 * - Else: Do pass `context` up to super-constructor. Superclass `what()` shall do the right thing.
111 * Do not save to `m_context_if_no_code`: our what() just forwards to superclass `what()`, so the lines
112 * that would ever access #m_context_if_no_code never execute.
113 *
114 * Note: Past versions of boost.system worked differently; it was possible to get by without this.
115 *
116 * ### Performance ###
117 * Using the above algorithm `context` is copied exactly once either way: either into #m_context_if_no_code or
118 * up into superclass's structures. The other guy then just gets a blank string.
119 */
120 const std::string m_context_if_no_code;
121}; // class Runtime_error
122
123// Free functions: in *_fwd.hpp.
124
125// Template implementations.
126
127template<typename Func, typename Ret>
128bool exec_and_throw_on_error(const Func& func, Ret* ret,
129 Error_code* err_code, util::String_view context)
130{
131 /* To really "get" what's happening, just pick an example invoker of the macro which invokes us.
132 * The background is in the FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() doc header; but to really picture the whole thing,
133 * just pick an example and work through how it all fits together.
134 *
135 * Note: This could also be done by simply stuffing all of the below into the macro and eliminating the
136 * present function. Well, we don't roll that way: we like debugger-friendliness, etc., etc.
137 * We use the preprocessor ONLY when what it provides cannot be done without it. */
138
139 if (err_code)
140 {
141 /* All good: the caller can assume non-null err_code; can perform func() equivalent and set *err_code on error.
142 * Our Runtime_error-throwing wrapping services are not required. */
143 return false;
144 }
145
146 /* err_code is null, so we have to make our own Error_code and throw an error if the wrapped operation actually
147 * sets it (indicating an error occurred). */
148 Error_code our_err_code;
149
150 /* Side note: It is assumed (but we don't enforce) that func() is the same as what would execute above in the
151 * `return false;` case. That's why typically the caller (usually an API; say, F(a, b, ..., err_code)) would
152 * use lambda/bind() to maka a functor F(e_c) that executes F(a, b, ..., e_c).
153 * So here we pass our_err_code as "e_c." */
154 *ret = func(&our_err_code);
155
156 if (our_err_code)
157 {
158 /* Error was detected: do our duty. Pass through the context info from caller.
159 * Note that passing in, say, FLOW_UTIL_WHERE_AM_I() would be less useful, since the present location
160 * is not helpful to the log reader in determining where the actual error first occurred. */
161 throw Runtime_error(our_err_code, context);
162 }
163
164 return true;
165} // exec_and_throw_on_error()
166
167template<typename Func>
168bool exec_void_and_throw_on_error(const Func& func, Error_code* err_code, util::String_view context)
169{
170 // See exec_and_throw_on_error(). This is just a simplified version where func() returns void.
171
172 if (err_code)
173 {
174 return false;
175 }
176
177 Error_code our_err_code;
178 func(&our_err_code);
179
180 if (our_err_code)
181 {
182 throw Runtime_error(our_err_code, context);
183 }
184
185 return true;
186} // exec_void_and_throw_on_error()
187
188} // namespace flow::error
189
190// Macros.
191
192/**
193 * Sets `*err_code` to `ARG_val` and logs a warning about the error using FLOW_LOG_WARNING().
194 * An `err_code` variable of type that is pointer to flow::Error_code must be declared at the point where the macro is
195 * invoked.
196 *
197 * @param ARG_val
198 * Value convertible to flow::Error_code. Reminder: `Error_code` is trivially/implicitly convertible from
199 * any error code set (such as `errno`s and boost.asio network error code set) that has been
200 * boost.system-enabled.
201 */
202#define FLOW_ERROR_EMIT_ERROR(ARG_val) \
203 FLOW_UTIL_SEMICOLON_SAFE \
204 ( \
205 ::flow::Error_code FLOW_ERROR_EMIT_ERR_val(ARG_val); \
206 FLOW_LOG_WARNING("Error code emitted: [" << FLOW_ERROR_EMIT_ERR_val << "] " \
207 "[" << FLOW_ERROR_EMIT_ERR_val.message() << "]."); \
208 *err_code = FLOW_ERROR_EMIT_ERR_val; \
209 )
210
211/**
212 * Identical to FLOW_ERROR_EMIT_ERROR(), but the message logged has flow::log::Sev::S_INFO severity instead of
213 * `S_WARNING`.
214 *
215 * @param ARG_val
216 * See FLOW_ERROR_EMIT_ERROR().
217 */
218#define FLOW_ERROR_EMIT_ERROR_LOG_INFO(ARG_val) \
219 FLOW_UTIL_SEMICOLON_SAFE \
220 ( \
221 ::flow::Error_code FLOW_ERROR_EMIT_ERR_LOG_val(ARG_val); \
222 FLOW_LOG_INFO("Error code emitted: [" << FLOW_ERROR_EMIT_ERR_LOG_val << "] " \
223 "[" << FLOW_ERROR_EMIT_ERR_LOG_val.message() << "]."); \
224 *err_code = FLOW_ERROR_EMIT_ERR_LOG_val; \
225 )
226
227/**
228 * Logs a warning about the given error code using FLOW_LOG_WARNING().
229 *
230 * @param ARG_val
231 * See FLOW_ERROR_EMIT_ERROR().
232 */
233#define FLOW_ERROR_LOG_ERROR(ARG_val) \
234 FLOW_UTIL_SEMICOLON_SAFE \
235 ( \
236 ::flow::Error_code FLOW_ERROR_LOG_ERR_val(ARG_val); \
237 FLOW_LOG_WARNING("Error occurred: [" << FLOW_ERROR_LOG_ERR_val << "] " \
238 "[" << FLOW_ERROR_LOG_ERR_val.message() << "]."); \
239 )
240
241/**
242 * Logs a warning about the (often `errno`-based or from a library) error code in `sys_err_code`.
243 * `sys_err_code` must be an object of type flow::Error_code in the context of the macro's invocation.
244 * See also FLOW_ERROR_SYS_ERROR_LOG_FATAL().
245 *
246 * Note this implies a convention wherein system (especially `errno`-based or from a libary) error codes are to
247 * be saved into a stack variable or parameter `flow::Error_code sys_err_code`. Of course if you don't like this
248 * convention and/or this error message, it is trivial to log something manually.
249 *
250 * It's a functional macro despite taking no arguments to convey that it mimics a `void` free function.
251 *
252 * The recommended (but in no way enforced or mandatory) pattern is something like:
253 *
254 * ~~~
255 * Error_code sys_err_code;
256 *
257 * // ...
258 *
259 * // sys_err_code has been set to truthy value. Decision has therefore been made to log about it but otherwise
260 * // recover and continue algorithm.
261 *
262 * FLOW_LOG_WARNING("(...Explain what went wrong; output values of interest; but not `sys_err_code` since....) "
263 * "Details follow.");
264 * FLOW_ERROR_SYS_ERROR_LOG_WARNING();
265 *
266 * // Continue operating. No assert(false); no std::abort()....
267 * ~~~
268 */
269#define FLOW_ERROR_SYS_ERROR_LOG_WARNING() \
270 FLOW_LOG_WARNING("System error occurred: [" << sys_err_code << "] [" << sys_err_code.message() << "].")
271
272/**
273 * Logs a log::Sev::S_FATAL message about the (often `errno`-based or from a library) error code in `sys_err_code`,
274 * usually just before aborting the process or otherwise entering undefined-behavior land such as via `assert(false)`.
275 * `sys_err_code` must be an object of type flow::Error_code in the context of the macro's invocation.
276 *
277 * This is identical to FLOW_ERROR_SYS_ERROR_LOG_WARNING(), except the message logged has FATAL severity instead
278 * of a mere WARNING. Notes in that macro's doc header generally apply. However the use case is different.
279 *
280 * The recommended (but in no way enforced or mandatory) pattern is something like:
281 *
282 * ~~~
283 * Error_code sys_err_code;
284 *
285 * // ...
286 *
287 * // sys_err_code has been set to truthy value that is completely unexpected or so unpalatable as to make
288 * // any attempt at recovery not worth the effort. Decision has therefore been made to abort and/or
289 * // enter undefined-behavior land.
290 *
291 * FLOW_LOG_FATAL("(...Explain what went wrong, and why it is very shocking; output values of interest; but not "
292 * "`sys_err_code` since....) Details follow.");
293 * FLOW_ERROR_SYS_ERROR_LOG_FATAL();
294 *
295 * // Enter undefined-behavior land.
296 * // Different orgs/projects do different things; but a decent approach might be:
297 * assert(false && "(...Re-explain what went wrong, so it shows up in the assert-trip message on some stderr.");
298 * // Possibly really abort program, even if assert()s are disabled via NDEBUG.
299 * std::abort();
300 * ~~~
301 */
302#define FLOW_ERROR_SYS_ERROR_LOG_FATAL() \
303 FLOW_LOG_FATAL("System error occurred: [" << sys_err_code << "] [" << sys_err_code.message() << "].")
304
305/**
306 * Narrow-use macro that implements the error code/exception semantics expected of most public-facing Flow (and
307 * Flow-like) class method APIs. The semantics it helps implement are explained in flow::Error_code doc header.
308 * More formally, here is how to use it:
309 *
310 * First, please read flow::Error_code doc header. Next read on:
311 *
312 * Suppose you have an API `f()` in some class `C` that returns type `T` and promises, in its doc header,
313 * to implement the error reporting semantics listed in the aforementioned flow::Error_code doc header. That is, if
314 * user passes in null `err_code`, error would cause `Run_time error(e_c)` to be thrown; if non-null, then
315 * `*err_code = e_c` would be set sans exception -- `e_c` being the error code explaining what went wrong,
316 * such as flow::net_flow::error::Code::S_WAIT_INTERRUPTED. Then here's how to use the macro:
317 *
318 * ~~~
319 * // Example API. In this one, there's a 4th argument that happens to follow the standard Error_code* one.
320 * // arg2 is a (const) reference, as opposed to a pointer or scalar, and is most efficiently handled by adding
321 * // cref() to avoid copying it.
322 * T f(AT1 arg1, const AT2& arg2, Error_code* err_code = 0, AT3 arg3 = 0)
323 * {
324 * FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(T, // Provide the return type of the API.
325 * // Forward all the args into the macro, but replace `err_code` => `_1`.
326 * arg1, arg2, _1, arg3);
327 * // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
328 *
329 * // ...Bulk of f() goes here! You can now set *err_code to anything without fear....
330 * }
331 * ~~~
332 *
333 * @see flow::Error_code for typical error reporting semantics this macro helps implement.
334 * @see exec_void_and_throw_on_error() which you can use directly when `ARG_ret_type` would be void.
335 * The present macro does not work in that case; but at that point using the function directly is concise enough.
336 * @see exec_and_throw_on_error() which you can use directly when `T` is a reference type.
337 * The present macro does not work in that case.
338 *
339 * @param ARG_ret_type
340 * The return type of the invoking function/method. It cannot be a reference type.
341 * In practice this would be, for example,
342 * `size_t` when wrapping a `receive()` (which returns # of bytes received or 0).
343 * @param ARG_function_name
344 * Suppose you wanted to, via infinite recursion, call the function -- from where you use this macro --
345 * and would therefore write a statement of the form `return F(A1, A2, ..., err_code, ...);`.
346 * `ARG_function_name` shall be the `F` part of such a hypothetical statement.
347 * Tip: Even in non-`static` member functions (methods), just the method name -- without the class name `::`
348 * part -- is almost always fine.
349 * Tip: However template args typically do have to be forwarded (e.g., in `template<typename T> X::f() {}`
350 * you'd supply `f<T>` as `ARG_function_name`), unless the compiler can infer them.
351 * Example: in a `bool X::listen(int x, Error_code* err_code, float y) {}` you'd have
352 * `{ return listen(x, err_code, y); }` and thus `ARG_function_name` shall be just `listen`.
353 * Tip: But, if there are 2+ template args, and the compiler cannot infer them, you need to
354 * surround the `x<a, b, ...>` with parentheses: like: `FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(int, (x<a, b>), ...)`.
355 * @param ...
356 * First see the premise in the doc header for `ARG_function_name` just above.
357 * Then the `...` arg list shall be as follows:
358 * `A1, A2, ..., _1, ...`. In other words it shall be the arg list for the invoking function/method as-if
359 * recursively calling it, but with the `err_code` arg replaced by the special identifier `_1`.
360 * Example: in a `bool X::listen(int x, Error_code* err_code, float y) {}` you'd have
361 * `{ return listen(x, err_code, y); }` and thus `...` arg list shall be: `x, _1, y`.
362 */
363#define FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(ARG_ret_type, ARG_function_name, ...) \
364 FLOW_UTIL_SEMICOLON_SAFE \
365 ( \
366 /* We need both the result of the operation (if applicable) and whether it actually ran. */ \
367 /* So we must introduce this local variable (note it's within a { block } so should minimally interfere with */ \
368 /* the invoker's code). We can't use a sentinel value to combine the two, since the operation's result may */ \
369 /* require ARG_ret_type's entire range. */ \
370 ARG_ret_type result; \
371 /* We provide the function: f(Error_code*), where f(e_c) == ARG_function_name(..., e_c, ...). */ \
372 /* Also supply context info of this macro's invocation spot. */ \
373 /* Note that, if f() is executed, it may throw Runtime_error which is the point of its existence. */ \
374 if (::flow::error::exec_and_throw_on_error \
375 ([&](::flow::Error_code* _1) -> ARG_ret_type \
376 { return ARG_function_name(__VA_ARGS__); }, \
377 &result, err_code, FLOW_UTIL_WHERE_AM_I_LITERAL(ARG_function_name))) \
378 { \
379 /* Aforementioned f() WAS executed; did NOT throw (no error); and return value was placed into `result`. */ \
380 return result; \
381 } \
382 /* else: */ \
383 /* f() did not run, because err_code is non-null. So now macro invoker should do its thing assuming that fact. */ \
384 /* Recall that the idea is that f() is just recursively calling the method invoking this macro with the same */ \
385 /* arguments except for the Error_code* arg, where they supply specifically `_1` by our contract. */ \
386 )
387
388/* Now that we're out of that macro's body with all the backslashes...
389 * Discussion of the FLOW_UTIL_WHERE_AM_I_LITERAL(ARG_function_name) snippet above:
390 *
391 * Considering the potential frequency that FLOW_ERROR_EXEC_AND_THROW_ON_ERROR() is invoked in
392 * an error-reporting API (such as flow::net_flow) -- even *without* an error actually being emitted --
393 * it is important we do not add undue computation. That macro does not take a context string, so it must
394 * compute it itself; as usual we use file/function/line for this. With the technique used above
395 * FLOW_UTIL_WHERE_AM_I_LITERAL() is replaced by a *string literal* -- like:
396 * "/cool/path/to/file.cpp" ":" "Class::someFunc" "(" "332" ")"
397 * which is as compile-time as it gets. So perf-wise that's fantastic; as good as it gets.
398 *
399 * Are there weaknesses? Yes; there is one: Per its doc header FLOW_UTIL_WHERE_AM_I_LITERAL(), due to having
400 * to be replaced by a literal, cannot cut out "/cool/path/to/" from __FILE__, even though in flow.log we do so
401 * for readability of logs. What if we wanted to get rid of this weakness? Then we cannot have a literal there;
402 * we can use other, not-fully-compile-time-computed FLOW_UTIL_WHERE_AM_I*(); that means extra computation
403 * at every call-site. That, too, can be worked-around: One can add an exec_and_throw_on_error() overload
404 * that would take 3 context args instead of 1: strings for file and function, int for line (supplied via
405 * __FILE__, #ARG_function, __LINE__); and it would only actually build a context string to pass to
406 * Runtime_error *if* (1) an error actually occurred; *and* (2) user in fact used err_code=null (meaning throw on
407 * error as opposed to return an Error_code); so lazy-evaluation.
408 *
409 * That would have been a viable approach but:
410 * - still slower (instead of a single compile-time-known pointer, at least 2 ptrs + 1 ints are passed around
411 * the call stack) at each call-site;
412 * - much slower on exception-throwing error (albeit this being relatively rare, typically; not at each call-site);
413 * - much, much more impl code.
414 *
415 * So it's cool; just don't massage __FILE__ in a totally flow.log-consistent way. */
An std::runtime_error (which is an std::exception) that stores an Error_code.
Definition: error.hpp:49
Runtime_error(const Error_code &err_code_or_success, util::String_view context="")
Constructs Runtime_error.
Definition: error.cpp:26
const std::string m_context_if_no_code
This is a copy of context from ctor if !err_code_or_success; or unused otherwise.
Definition: error.hpp:120
const char * what() const noexcept override
Returns a message describing the exception.
Definition: error.cpp:46
Flow module that facilitates working with error codes and exceptions; essentially comprised of niceti...
Definition: error.cpp:22
bool 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.
Definition: error.hpp:168
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
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
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:508