Skip to content

Commit 7de444a

Browse files
authored
Merge pull request #223 from thelfer/191-document-error-handling
191 document error handling
2 parents 6b4255d + 38a9397 commit 7de444a

4 files changed

Lines changed: 359 additions & 5 deletions

File tree

docs/web/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ if(MGIS_HAVE_PANDOC)
6363
mgis_pandoc_generate_html_page(index "-c css/slideshow.css")
6464
mgis_pandoc_generate_html_page(install)
6565
mgis_pandoc_generate_html_page(contributing)
66+
mgis_pandoc_generate_html_page(error_handling "--toc" "--toc-depth=3")
6667
mgis_pandoc_generate_html_page(bindings-cxx "--toc" "--toc-depth=3")
6768
mgis_pandoc_generate_html_page(bindings-python-small_strain "--toc" "--toc-depth=3")
6869
mgis_pandoc_generate_html_page(bindings-python-finite_strain "--toc" "--toc-depth=3")

docs/web/error_handling.md

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
---
2+
title: Error handling in `MGIS`
3+
author: Thomas Helfer
4+
date: 2025
5+
lang: en-EN
6+
numbersections: true
7+
documentclass: article
8+
from: markdown+tex_math_single_backslash
9+
geometry:
10+
- margin=2cm
11+
papersize: a4
12+
link-citations: true
13+
colorlinks: true
14+
figPrefixTemplate: "$$i$$"
15+
tabPrefixTemplate: "$$i$$"
16+
secPrefixTemplate: "$$i$$"
17+
eqnPrefixTemplate: "($$i$$)"
18+
bibliography: bibliography.bib
19+
---
20+
21+
This section describes the error handling strategy introduced in
22+
`MGIS` Version 3.1 which is meant to:
23+
24+
1. ensure performances and compatibility with the HPC techniques used
25+
in the code (MPI currently and eventually OpenMP in the future).
26+
2. provide rich error messages to the end-user.
27+
28+
The first point eliminates the use of exceptions in most cases (see
29+
below the special cases of constructors).
30+
31+
Some other standard strategies provided by the standard have also been
32+
discarded, although their design have inspired the proposed solution.
33+
For example, `std::error_code` only allows
34+
to use predefined error messages that are not able to describe the
35+
context.
36+
37+
A function or a method using this error handling strategy is
38+
recognizable as follows:
39+
40+
- It must take a class derived from `AbstractErrorHandler&` as its first
41+
parameter. Two main classes, derived from `AbstractErrorHandler&`, are
42+
available: `ContractViolationHandler` and `Context`. Consequently, if
43+
a function has a `AbstractErrorHandler&` as its first parameter, the
44+
reader knows that it may fail, and that the return type of the
45+
function should be interpreted according to the rule below.
46+
- It returns a value that can be invalid. The most common return type is
47+
a boolean which indicates success (`true`) or failure (`false`).
48+
- It must be declared `noexcept`.
49+
50+
# Returned value of functions (or methods) that may fail
51+
52+
A function that may fail shall generally return either:
53+
54+
- a boolean associated with the success of the function, i.e. if a
55+
function returns `true`, it succed.
56+
- an `std::optional` object holding the results if those results are
57+
stored on the stack.
58+
- an `std::unique_ptr` or an `std::shared_ptr` object holding if those
59+
results are stored on the heap. In this case, the failure of the
60+
function is indicated by the fact that the underlying pointer is null.
61+
- a class to which an `InvalidValue` object is implicitly convertible.
62+
63+
> ** Note **
64+
>
65+
> Those returned value shall be mandatory marked with the `[[nodiscard]]`
66+
> attribute. However, this conflicts in many case with the `MGIS_EXPORT`
67+
> attribute. This is a defect of `C++-20`.
68+
69+
# `ContractViolationHandler` and `Context`
70+
71+
Both classses `ContractViolationHandler` and `Context` inherit from
72+
`AbstractErrorHandler`.
73+
74+
## `ContractViolationHandler`
75+
76+
`ContractViolationHandler` is designed to report a contract violation,
77+
i.e. errors that shall not occur by design.
78+
79+
`ContractViolationHandler` can be used in a `constexpr` context, if no
80+
contrat violation is detected. If a contract violation is detected, a
81+
compile-time error is generated since `registerErrorMessage` is not
82+
`constexpr`
83+
84+
### Reporting error to end-user
85+
86+
The `ContractViolationHandler` class provides the `registerErrorMessage`
87+
method which accepts a `C`-string.
88+
89+
The behaviour of this class is to call `std::abort` in case of contract
90+
violation. This behaviour can be changed by passing
91+
`-Denable-exceptions=ON` to `cmake` when compiling `MGIS`.
92+
93+
## `Context`
94+
95+
The `Context` class is used for standard error management.
96+
97+
### Reporting error to end-user
98+
99+
The `Context` class inherits from the `ErrorBacktrace` class, which has
100+
been designed to store error messages in a hierarchical way from the
101+
lowest level of the code up to the highest level function. The
102+
`ErrorBacktrace` is meant to be used through the `Context` object which
103+
is passed as the first argument to most functions.
104+
105+
The `ErrorBacktrace` class, and the `Context` class mostly provides the
106+
`registerErrorMessage` method that can register an error in the form of:
107+
108+
1. a `C`-string
109+
2. a `C++`-string
110+
3. a couple of a function allowing to return an error message from an
111+
integer value. This kind of function is provided by many HPC
112+
libraries.
113+
114+
For convenience, the `registerErrorMessage` always returns an invalid
115+
value, i.e. a value that is convertible to any of the returned type
116+
described in the previous section.
117+
118+
By default, the error messages are packed up to the moment when error(s)
119+
must be reported to the end-user. The error messages can then be
120+
retrieved by the `getErrorMessage` method (or `getRawErrorMessage`, see
121+
below). However, if `MGIS` is compiled with the flag
122+
`-Denable-exceptions=ON`, an exception is thrown instead using
123+
`mgis::raise`.
124+
125+
### Source location
126+
127+
In debug mode, the source location, as returned by the
128+
`std::source_location::current_location` method, is automatically added
129+
to the error message returned by the `getErrorMessage` method.
130+
131+
Note that this feature is currently only supported by `gcc` compilers.
132+
133+
In some cases, the source location is not meaningful. In this case, the
134+
`registerErrorMessageWithoutSourceLocation` method can be used.
135+
136+
If the information about the source location are not wanted, the
137+
`getRawErrorMessage` method can be used.
138+
139+
## Example of usage of the `registerErrorMessage` method
140+
141+
The following code illustrates the usage of the `ErrorBacktrace`,
142+
through a `Context` object:
143+
144+
~~~~{.cxx}
145+
void processNext(){};
146+
147+
bool f3(Context &ctx)
148+
{
149+
// for this example, the message is useless
150+
// In pratice, one shall report the cause of the error
151+
// and not expose details, like the function name.
152+
//
153+
// Examples:
154+
//
155+
// - "negative temperature detected"
156+
// - "non convergence of the nonlinear solver"
157+
//
158+
// Note that the function name, the source file and the
159+
// line number are automatically added in debug mode.
160+
return ctx.registerErrorMessage("invalid call");
161+
}
162+
163+
bool f2(Context &ctx)
164+
{
165+
if (!f3(ctx)) {
166+
// f2 fails, but we don't have any more information
167+
// to add for the end user (i.e. f3 is an internal
168+
// method and a message like `f3 failed` is not
169+
// meaningful), so we just return
170+
return false;
171+
}
172+
processNext();
173+
return true;
174+
}
175+
176+
bool f1(Context &ctx)
177+
{
178+
if (!f2(ctx)) {
179+
return e.registerErrorMessage("invalid call to f2");
180+
}
181+
processNext();
182+
return true;
183+
}
184+
~~~~
185+
186+
If the `f1` function is called, the
187+
following error message is generated in release mode:
188+
189+
~~~~ bash
190+
invalid call to f2
191+
* invalid call
192+
~~~~
193+
194+
In debug mode, the following message is generated:
195+
196+
~~~~{.bash}
197+
/home/UserDir/tests/core/error_backtrace_handler/error_backtrace_handler_test.cpp:31: in function 'bool f1(mgis::ErrorBacktrace&)': invalid call to f2
198+
* /home/UserDir/tests/core/error_backtrace_handler/error_backtrace_handler_test.cpp:14: in function 'bool f3(mgis::ErrorBacktrace&)': invalid call
199+
~~~~
200+
201+
### Warning about usage of `C`-strings
202+
203+
Note that in the case of a `C`-string, the string is not copied and only
204+
the pointer is stored. The developer must then ensure that this string
205+
is not destroyed. As a rule of thumb, this string shall belong to the
206+
data section of the binary.
207+
208+
### Warning about usage of `C++`-strings
209+
210+
`C++`-strings are the best way to report context sensitive error message.
211+
However, to reduce code bloat, building a complex error message shall
212+
never be implemented in a template function: one shall create a
213+
dedicated non-template function implemented in a source file.
214+
215+
# The special case of constructors
216+
217+
Constructors don't return values. There are mostly two ways to handle
218+
failure in constructors:
219+
220+
- exceptions
221+
- having a boolean data member stating if the object is valid (this
222+
strategy is used by the standard `iostream` library for instance).
223+
224+
Here, we propose to use exceptions and to wrap constructors in dedicated
225+
functions.
226+
227+
## The `raise` function
228+
229+
The `raise` function is an utility function to throw exception in a safe
230+
way: building the exception is not done in the `throw` statement. This
231+
is required to avoid a potential undefined behaviour if the constructor
232+
of the exception throws.
233+
234+
The type of the exception thrown is given by the first template argument
235+
of the `raise` function and defaults to `std::runtime_exception`.
236+
237+
## The `construct` function
238+
239+
The `construct` function calls the constructor of an object that may
240+
throw an exception and returns a `std::optional` object that holds the
241+
object if the call to the constructor did not throw an exception.
242+
243+
If the constructor threw an exception, then the error message hold by
244+
the exception is registered in the instance of the `ErrorBacktrace`
245+
class which is passed as the first argument of the function and an empty
246+
optional object is returned.
247+
248+
Aside from the first argument (a reference to an instance of the
249+
`ErrorBacktrace` class), all the other arguments are forwarded to the
250+
constructor of the object.
251+
252+
### The `MGIS_CONSTRUCT` macro
253+
254+
The `MGIS_CONSTRUCT` macro is a wrapper
255+
around the `construct` function which adds
256+
the current source location when required (typically in the `debug`
257+
mode).
258+
259+
### The `MGIS_TRY_CONSTRUCT` macro
260+
261+
A typical pattern of usage of the `construct` function (through the
262+
`MGIS_CONSTRUCT` macro) is to try to build an object and:
263+
264+
1. to defer the result in case of success.
265+
2. to stop the execution of the current function in case of failure.
266+
267+
Here is a typical example of this pattern:
268+
269+
~~~~{.cxx}
270+
auto tmp_v = MGIS_CONSTRUCT(ObjectType, e, ...);
271+
if(!tmp_v.has_value()){
272+
return false;
273+
}
274+
auto& v = *(tmp_v);
275+
~~~~
276+
277+
The `MGIS_TRY_CONSTRUCT` macro reduces
278+
this code as follows:
279+
280+
~~~~{.cxx}
281+
MGIS_TRY_CONSTRUCT(ObjectType, v, e, ...);
282+
// Here you can work with variable v which is a reference
283+
// to the ObjectType built by the "construct" function
284+
~~~~
285+
286+
## The `make_unique` function
287+
288+
The `make_unique` function tries to allocate an object on the heap and
289+
stores it in a `std::unique_ptr`.
290+
291+
If an exception is thrown during the construction of the object, the
292+
error message held by the exception is registered in an instance of the
293+
`ErrorBacktrace` and an empty pointer is
294+
returned.
295+
296+
### Helper macros for the `make_unique` function
297+
298+
The `MGIS_MAKE_UNIQUE` and
299+
`MGIS_TRY_MAKE_UNIQUE` macros are similar
300+
to the `MGIS_CONSTRUCT` and
301+
`MGIS_TRY_CONSTRUCT` macros respectively.
302+
303+
## The `make_unique_as` function
304+
305+
The `make_unique_as` function is similar to the `make_unique` function
306+
except that the built object is stored in a `std::unique_ptr` of some
307+
base class. This method is useful in a polymorphic context.
308+
309+
### Helper macros for the `make_unique_as` function
310+
311+
The `MGIS_MAKE_UNIQUE_AS` and `MGIS_TRY_MAKE_UNIQUE_AS` macros are
312+
similar to the `MGIS_CONSTRUCT` and `MGIS_TRY_CONSTRUCT` macros
313+
respectively.
314+
315+
## The `make_shared` function
316+
317+
The `make_shared` function is similar to the `make_unique` function
318+
except that the result is stored in a `std::shared_ptr`.
319+
320+
### Helper macros for the `make_shared` function
321+
322+
The `MGIS_MAKE_SHARED` and `MGIS_TRY_MAKE_SHARED` macros are similar to
323+
the `MGIS_CONSTRUCT` and `MGIS_TRY_CONSTRUCT` macros respectively.
324+
325+
## The `make_shared_as` function
326+
327+
The `make_shared_as` function is similar to the `make_unique_as` function
328+
except that the result is stored in a `std::shared_ptr`.
329+
330+
### Helper macros for the `make_shared_as` function
331+
332+
The `MGIS_MAKE_SHARED_AS` and `MGIS_TRY_MAKE_SHARED_AS` macros are
333+
similar to the `MGIS_CONSTRUCT` and `MGIS_TRY_CONSTRUCT` macros
334+
respectively.
335+
336+
# Interaction with an external library that may use exceptions
337+
338+
## The `registerExceptionInErrorBacktrace` function
339+
340+
Call to external libraries that relies on the usage of exceptions must
341+
be encapsulated in appropriate `try/catch`
342+
blocks as follows:
343+
344+
~~~~{.cxx}
345+
try{
346+
....
347+
} catch(...)
348+
registerExceptionInErrorBacktrace(e);
349+
}
350+
~~~~
351+
352+
The `registerExceptionInErrorBacktrace` is a Lippincott-like helper
353+
function which translate exceptions derived from `std::exception` into
354+
error messages.
355+
356+
If the external library to be used, used another exception hierarchy,
357+
then appropriate versions of this helper function shall be created.

docs/web/mgis-template.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<li><a href="https://thelfer.github.io/mgis/doxygen/index.html">Doxygen documentation</a></li>
5656
<li><a href="bindings-cxx.html">Description of the C++ library</a></li>
5757
<li><a href="install.html">Installation guide</a></li>
58+
<li><a href="error_handling.html">Error handling</a></li>
5859
<li><a>Bindings</a>
5960
<ul>
6061
<li><a>python</a>

include/MGIS/Context.hxx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ namespace mgis {
3131
* \brief a class used to pass an execution context to most methods of
3232
* `MGIS` and gather information (error, logs).
3333
*
34-
* The `Context` may be changed at various stage of the computation.
35-
* For example, the verbosity level or the logging stream
36-
* can be changed when calling a new model: this is can useful
37-
* to debug a specific rm.
38-
*
3934
* The default logging stream is the one returned by the
4035
* `mgis::getDefaultLogStream` free function.
4136
*/

0 commit comments

Comments
 (0)