mirror of
https://github.com/boostorg/leaf.git
synced 2026-02-10 11:32:30 +00:00
03f7adf0d778f0def2c995e3600890f0be6d696e
:sourcedir: .
:last-update-label!:
:icons: font
= LEAF
Low-latency Error Augmentation Framework for C++11
:toclevels: 3
:toc: left
:toc-title:
[abstract]
== Abstract
LEAF is a non-intrusive C++11 library for transporting arbitrary error information from contexts that detect and report failures, as well as from intermediate error-neutral contexts, to scopes where the error is ultimately handled.
LEAF does not allocate dynamic memory, which makes it suitable for low-latency and other performance-critical environments. It is compatible with all error handling APIs, and can be used with or without exception handling.
== Distribution
LEAF is distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0].
The source code is available in https://github.com/zajo/leaf[this GitHub repository].
NOTE: LEAF is not part of Boost. Please post questions and feedback on the Boost Developers Mailing List.
== Building
LEAF is a header-only library and it requires no building. The unit tests use Boost Build, but the library itself has no dependency on Boost or any other library.
== Portability
LEAF requires a {CPP}11 compiler. See unit test matrix at https://travis-ci.org/zajo/leaf[Travis-CI].
== Tutorial
Let's consider a function that reads data from a file into a buffer and returns an `error` enum to report errors:
====
[source,c++]
----
error file_read( FILE & f, void * buf, int size ) {
int n = fread(buf,1,size,&f);
if( ferror(&f) ) { <1>
leaf::put( ei_errno { errno } );
return file_read_error;
}
if( n!=size ) <2>
return file_read_error;
return ok;
}
----
<1> The function fails if `ferror` indicates an error.
<2> It also fails if `fread` reports that it didn't read all of the data requested.
====
In the first case, there is a relevant `errno` code, so the function uses `<<put,put>>` to make it available later when the error is handled.
The second condition, while treated as a failure by our `file_read` function, is not a failure as far as `fread` is concerned, and therefore `errno` is not available. So in this case we simply return `file_read_error`.
The `ei_errno` struct is a simple error info type, defined as follows:
[source,c++]
----
struct ei_errno { int value; };
----
The user can define any number of such types and, when an error is detected, pass them to `<<put,put>>` to make relevant information available to error handling contexts, if needed.
NOTE: Error info types must define a public data member called `value` and must provide a `noexcept` move constructor and `noexcept` destructor. Any number of valid error info objects may be passed in a single call to `<<put,put>>`.
Now, let's consider a possible caller of `file_read`:
====
[source,c++]
----
error print_file( char const * file_name ) {
std::shared_ptr<FILE> f;
if( error err = file_open(file_name,f) ) <1>
return err;
auto put = leaf::preload( ei_file_name {file_name } ); <2>
int s;
if( error err = file_size(*f,s) )
return err;
std::string buffer( 1+s, '\0' );
if( error err = file_read(*f,&buffer[0],buffer.size()-1) )
return err;
std::cout << buffer;
std::cout.flush();
if( std::cout.fail() )
return cout_error;
put.cancel();
return ok;
}
----
<1> This function takes a `file_name` argument and calls another function, `file_open` (not shown), that opens the file for reading. If that function reports a failure, we simply forward it to the caller.
<2> Next, we call the convenience function `<<preload,preload>>`, moving an `ei_file_name` error info object into the temporary object `put`. Unless canceled, when this object is destroyed, all error info objects passed to `preload` will be forwarded by rvalue reference to `<<put,put>>` automatically. This way we can rest assured that the file name will be available with any failure reported by the several `return` statements in `print_file`.
====
TIP: `print_file` uses preload only after `file_open` (not shown) has successfully opened the file. That's because, presumably, `file_open` itself has the file name and will have already passed it to `<<put,put>>`.
NOTE: The `ei_file_name` type, similarly to `ei_errno`, is a simple struct containing a string `value`, e.g. `struct ei_file_name { std::string value; };`
If all functions called by `print_file` succeed, we call `put.<<cancel,cancel>>()`, to instruct its destructor to not forward the preloaded `ei_file_name` object to `<<put,put>>`, and return `ok`.
TIP: If failures are reported by throwing exceptions, it is not necessary to call `<<cancel,cancel>>`; LEAF will use `std::unhandled_exception` to determine if a failure is being reported.
Finally, let's consider the `main` function, which is able to handle errors reported by `print_file`:
====
[source,c++]
----
int main( int argc, char const * argv[ ] ) {
char const * fn;
if( error err=parse_command_line(argc,argv,fn) ) { <1>
std::cout << "Bad command line argument" << std::endl;
return 1;
}
leaf::expect<ei_file_name,ei_errno> info; <2>
switch( error err=print_file(fn) ) {
case ok:
return 0;
case file_open_error: <3>
unwrap( info.match<ei_file_name,ei_errno>( [ ] ( std::string const & fn, int errn ) {
if( errn==ENOENT )
std::cerr << "File not found: " << fn << std::endl;
else
std::cerr << "Failed to open " << fn << ", errno=" << errn << std::endl;
} ) );
return 2;
case file_size_error:
case file_read_error: <4>
unwrap(
info.match<ei_file_name,ei_errno>( [ ] ( std::string const & fn, int errn ) {
std::cerr << "Failed to access " << fn << ", errno=" << errn << std::endl;
} ),
info.match<ei_errno>( [ ] ( int errn ) {
std::cerr << "I/O error, errno=" << errn << std::endl;
} ),
info.match<>( [ ] {
std::cerr << "I/O error" << std::endl;
} ) );
return 3;
default: <5>
std::cerr <<
"Unknown error code " << err << ", cryptic information follows." << std::endl <<
leaf::diagnostic_information();
return 4;
}
}
----
<1> Parse the command line to obtain a file name.
<2> Tell LEAF that in case `print_file` reports an error, we expect to possibly have error info of type `ei_file_name` and/or `ei_errno` available.
<3> In case `print_file` reports a `file_open_error`, if both `ei_file_name` and `ei_errno` are available, the call to `<<match,match>>` will succeed, and then `<<unwrap,unwrap>>` will pass both the `ei_file_name::value` and `ei_errno::value` to the supplied lambda. But if either `ei_file_name` or `ei_errno` is not available, `unwrap` will throw `<<mismatch_error,mismatch_error>>`, having failed to find a suitable `<<match,match>>`. Presumably (since this program does not use exception handling), this indicates that receiving a `file_open_error` without both `ei_file_name` and `ei_errno` available is a logic error.
<4> Here we provide identical handling for either `file_size_error` or `file_read_error`, by first trying to `<<match,match>>` both `ei_file_name` and `ei_errno`; but if that fails, we're prepared to deal with an error condition where only `ei_errno` is available. If neither is available, the final `<<match,match>>` will print a generic error message, thus guaranteeing that this call to `unwrap` will never throw.
<5> Finally, the `default` case is designed to help diagnose logic errors where we got an error code which we forgot to handle. It prints the unrecognized error code, followed by `<<diagnostic_information,diagnostic_information>>`, which will print a complete, if not user-friendly, list of all available error info.
====
NOTE: The complete program from this tutorial is available https://github.com/zajo/leaf/blob/master/example/print_file_ec.cpp[here]. There is also https://github.com/zajo/leaf/blob/master/example/print_file_eh.cpp[another] version of the same program that uses exception handling to report errors.
[[reference]]
== Reference
[[available]]
=== `available`
====
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
class available {
available( available const & ) = delete;
available & operatar=( available const & ) = delete;
public:
available() noexcept;
~available() noexcept
void set_to_propagate() noexcept;
[[noreturn]] void rethrow_with_current_info();
template <class... ErrorInfo,class F>
<<unspecified_type>> match( F && f ) noexcept;
};
} }
----
====
Class `available` is used to access any error info objects currently available in the calling thread (see `<<put,put>>`). Objects of class `available` are not copyable or moveable.
NOTE: Typically the functionality provided by class `available` is accessed through instancing the `<<expect,expect>>` class template, which derives from class `available`.
'''
[[available_ctor]]
==== Constructor
[source,c++]
----
namespace boost { namespace leaf {
available::available() noexcept;
} }
----
Effects: :: Initializes an `available` instance so that when it is destroyed it will reset (clear) all error info objects that are currently available in the calling thread. This behavior can be disabled by a call to `<<set_to_propagate,set_to_propagate>>`.
'''
[[available_dtor]]
==== Destructor
[source,c++]
----
namespace boost { namespace leaf {
available::~available() noexcept;
} }
----
Effects: :: Unless the user has called `<<set_to_propagate,set_to_propagate>>`, resets (clears) all error info objects that are currently available in the calling thread.
'''
[[available::set_to_propagate]]
==== `set_to_propagate`
[source,c++]
----
namespace boost { namespace leaf {
void available::set_to_propagate() noexcept;
} }
----
Effects: :: By default, `<<available_dtor,~available>>` will reset (clear) all error info objects that are currently available in the calling thread (see `<<put,put>>`). Call `set_to_propagate` to disable this behavior.
'''
[[available::rethrow_with_current_info]]
==== `rethrow_with_current_info`
[source,c++]
----
namespace boost { namespace leaf {
void available::rethrow_with_current_info();
} }
----
Effects: :: Equivalent to:
+
[code,c++]
----
set_to_propagate();
throw;
----
'''
[[available::match]]
==== `match`
[source,c++]
----
namespace boost { namespace leaf {
template <class... ErrorInfo,class F>
<<unspecified_type>> available::match( F && f ) noexcept;
} }
----
Returns: :: An object of unspecified type designed to be passed directly to `<<unwrap,unwrap>>`, which takes any number of such objects, and proceeds to inspect them in order, until it finds a match where error info objects are currently available in the calling thread (see `<<put,put>>`) for all specified `ErrorInfo...` types. If found, `unwrap` invokes `f`, passing the `.value` of each available error info object.
Throws: :: If `unwrap` is unable to find a suitable match, it throws `<<mismatch_error,mismatch_error>>`.
'''
[[expect]]
=== `expect<>`
====
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... ErrorInfo>
class expect: public availablej {
expect( expect const & ) = delete;
expect & operator=( expect const & ) = delete;
public:
expect() noexcept;
~expect() noexcept;
};
} }
----
====
The `expect` class template is used to communicate to LEAF that error info objects of the specified `ErrorInfo...` types are expected in the current scope, to help handle failures.
`expect` objects are not copyable or movable. They form a hierarchy, such that error info types requested higher up the call chain remain open in lower scopes, regardless of whether or not they're specified in lower level `expect` instances.
'''
[[expect_ctor]]
==== Constructor
[source,c++]
----
namespace boost { namespace leaf {
expect<class ErrorInfo...>::expect() noexcept;
} }
----
Effects: ::
. Provides storage for objects of the specified `ErrorInfo...` types, enabling the `<<put,put>>` function template for use with these types within the current scope. When an error info object is passed to `put`, it is discarded unless the call originates in a scope where that specific error info type is expected.
. Resets (clears) all error info objects that are currently available. Note, the reset is _not_ limited to the specified `ErrorInfo...` types.
'''
[[expect_dtor]]
==== Destructor
[source,c++]
----
namespace boost { namespace leaf {
expect<class ErrorInfo...>::~expect*( noexcept;
} }
----
Effects: ::
. The storage provided by the `expect` constructor for error info objects is removed, except for error info types specified in other active `expect` instances up the call stack.
. If `std::unhandled_exception()` returns `true`, calls `<<set_to_propagate,set_to_propagate`>>.
'''
[[unwrap]]
=== `unwrap`
====
.#include <boost/leaf/expect.hpp>
[source,c++]
----
namespace boost { namespace leaf {
struct mismatch_error: std::exception { };
template <class... Match>
void unwrap( Match && ... m );
} }
----
====
Effects: :: `unwrap` takes any number of objects returned by `<<match,match>>`, and proceeds to inspect them in order, until it finds a match where error info objects are currently available in the calling thread (see `<<put,put>>`) for all `ErrorInfo...` types used to instantiate the `<<match,match>>` function template. If found, `unwrap` invokes `f`, passing the `.value` of each available error info object.
Throws: :: If no match is found, `unwrap` throws `<<mismatch_error>>`.
'''
[[put]]
=== `put`
====
.#include <boost/leaf/put.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... ErrorInfo>
void put( ErrorInfo && ... info ) noexcept;
} }
----
====
Effects: :: Moves each specified `info` object of type that is expected in the calling thread, into the storage provided by `<<expect,expect>>`. All such `info` objects can be accessed by objects of class `<<available,available>>` or of an instance of the `expect` template.
+
All other `info` objects are discarded.
'''
[[throw_with_info]]
=== `throw_with_info`
====
.#include <boost/leaf/put.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... ErrorInfo,class Exception>
[[noreturn]] void throw_with_info( Exception const & e, ErrorInfo && ... info );
} }
----
====
Effects: :: As if:
+
[source,c++]
----
put(std::forward<T>(info)...);
throw e;
----
'''
[[preload]]
=== `preload`
====
.#include <boost/leaf/put.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... ErrorInfo>
<<unspecified_type>> preload( ErrorInfo && ... info );
} }
----
====
Returns: :: An object of unspecified moveable type which holds copies of all the passed `info` objects. Upon its destruction the stored copies are all forwarded by rvalue reference to `<<put,put>>`, except that:
- If `std::unhandled_exception()` is true, or the user calls `cancel` (a member function of the returned object), all preloaded error info objects are discarded.
- If any of the `info` objects passed to `preload` is a function, it is expected to return the actual error info object to be passed to `put`, and the function call to obtain it is deferred until the object returned by `preload` is destroyed (think `errno`, which obviously should not be captured at the time `preload` is called).
'''
=== `has_current_error`
====
.#include <boost/leaf/has_current_error.hpp>
[source,c++]
----
namespace boost { namespace leaf {
bool has_current_error() noexcept;
void set_has_current_error( bool (*f)() ) noexcept;
} }
----
====
LEAF uses `has_current_error` to determine if an error is currently being propagated up the call stack. By default, `has_current_error` returns `std::unhandled_exception()`. Use `set_has_current_error` to hook up a different function, if needed
NOTE: `has_current_error` is an optimization, for example when using `<<preload,preload>>`, the call to `<<put,put>>` will be skipped unless `has_current_error` returns `true`. It is valid to pass to `set_has_current_error` a function which always returns `true`.
'''
=== Common error info types
====
.#include <boost/leaf/common.hpp>
[source,c++]
----
namespace boost { namespace leaf {
struct ei_api_function { char const * value; };
struct ei_file_name { std::string value; };
struct ei_errno {
int value;
friend std::ostream & operator<<( std::ostream & os, ei_errno const & err );
};
ei_errno get_errno() noexcept {
return ei_errno { return errno };
}
} }
----
====
This header defines some common error info objects which can be used directly:
- The `ei_api_function` type is designed to capture the name of the function for which a failure is reported. For example, if you're reporting an error detected by `fread`, you could use `leaf::ei_api_function { "fread" }`.
+
WARNING: The passed value is stored as a C string, so you should only pass string literals for `value`.
- When a file operation fails, you could use `ei_file_name` to capture the name of the file.
- `ei_errno` is suitable to capture `errno`.
+
TIP: If using `<<preload,preload>>`, please pass `&get_errno` instead of an instance of `ei_errno`; this way `errno` will be captured after the error is detected, rather than at the time `preload` is called.
+
NOTE: `ei_errno` objects can be stremed to a `std::ostream`, which uses `strerror` to convert the `errno` code to a friendlier error message. This is designed for use with `<<diagnostic_information,diagnostic_information>>`.
'''
=== `diagnostic_information`
====
.#include <boost/leaf/diagnostic_information.hpp>
[source,c++]
----
namespace boost { namespace leaf {
class diagnostic_information {
diagnostic_information( diagnostic_information const & ) = delete;
diagnostic_information & operator=( diagnostic_information const & ) = delete;
public:
diagnostic_information() noexcept();
friend std::ostream & operator<<( std::ostream &, diagnostic_information const );
};
} }
----
====
The only operation supported by class `diagnostic_information` is `operator<<` which, when used with a `std::ostream` outputs a developer-friendly string representation of all of the currently available error info objects.
Each error info object is output based on the following rules:
- If its type defines a suitable `operator<<` overload, it is used by the `operator<<` overload for `diagnostic_information` directly; otherwise
- If the type of its `value` data member defines a suitable `operator<<` overload, it will be used instead.
- Otherwise the error info type can not be output by `diagnostic_information`. This is valid, using such error info types will not result in a compile error.
'''
=== `current_exception_diagnostic_information`
====
.#include <boost/leaf/current_exception_diagnostic_information.hpp>
[source,c++]
----
namespace boost { namespace leaf {
class current_exception_diagnostic_information {
current_exception_diagnostic_information( current_exception_diagnostic_information const & ) = delete;
current_exception_diagnostic_information & operator=( current_exception_diagnostic_information const & ) = delete;
public:
current_exception_diagnostic_information() noexcept();
friend std::ostream & operator<<( std::ostream &, current_exception_diagnostic_information const );
};
} }
----
====
The only operation supported by class `current_exception_diagnostic_information` is `operator<<` which, when used with a `std::ostream` outputs a developer-friendly information about the current unhandled exception, followed by the output of `<<diagnostic_information,diagnostic_information>>`.
Typical use for `current_exception_diagnostic_information` is:
[source,c++]
----
catch(...) {
std::cerr << Unhandled exception! << std::endl <<
leaf::current_exception_diagnostic_information();
}
----
'''
=== `capture`
====
.#include <boost/leaf/capture.hpp>
[source,c++]
----
namespace boost { namespace leaf {
class capture {
capture( capture const & ) = delete;
capture & operator=( capture const & ) = delete;
public:
capture() noexcept;
capture( capture && ) noexcept;
release() noexcept;
};
} }
----
====
Objects of class `capture` can be used to transport the currently available error info objects from one thread to another.
NOTE: To transport error info objects between threads, it is usually preferable to use the higher level functions `<<transport,transport>>` and `<<get,get>>`, though they require exception handling.
'''
==== Constructors
[source,c++]
----
namespace boost { namespace leaf {
capture::capture() noexcept;
capture::capture( capture && ) noexcept;
} }
----
Effects: ::
- The default constructor moves all of the currently available (in the calling thread) exception info objects into a dynamically-allocated buffer stored in the `capture` object.
- The move constructor does not throw.
'''
==== `release`
[source,c++]
----
namespace boost { namespace leaf {
void capture::release() noexcept;
} }
----
Moves all exception info objects captured from the thread in which `this` was initialized, and makes them available in the calling thread.
'''
=== `transport`
====
.#include <boost/leaf/transport.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class... ErrorInfo,class F>
<<unspecified>> transport( F f )
} }
----
====
Returns: :: A function object which, when called:
. Performs the same operations as the constructor of `<<expect,expect>><ErrorInfo...>`, then
. forwards all of its arguments to `f`, and returns the return value of `f`.
The returned function is designed to be used as a wrapper for `f` when it's passed to `std::async` or `std::packaged_task` and launched in a worker thread.
Later the user is expected to call `<<get,get>>` instead of `std::future::get` directly; this way, in case `f` throws, all of the error info objects are effectivey transported (together with the exception object) from the worker thread into the waiting thread.
NOTE: Click https://github.com/zajo/leaf/blob/master/example/transport_between_threads.cpp[here] to see a complete example on transporting error info objects between threads.
'''
=== `get`
====
.#include <boost/leaf/transport.hpp>
[source,c++]
----
namespace boost { namespace leaf {
template <class Future>
decltype(std::declval<Future>().get()) get( Future & f );
} }
----
====
Effects: :: This function simply returns `f.get()`, expecting that `f` is of type `std::future<>` or another similar type that defines a `get` member function, to obtain the result from a worker thread started using `<<transport,transport>>`. In case the worker thread throws, all error info objects from the worker thread are automatically made available in the calling thread.
TIP: There is no need to use `<<expect,expect>>` when calling `<<get,get>>`; in case a worker thread throws an exception, _all_ available error info objects are trasported and made available in the calling thread.
NOTE: Click https://github.com/zajo/leaf/blob/master/example/transport_between_threads.cpp[here] to see a complete example on transporting error info objects between threads.
== Programming techniques
=== Capturing `errno` with `preload`
Typically, when calling `<<preload,preload>>` we pass in the actual error info object(s) that we want forwarded to `<<put,put>>`. This copies them into the returned temporary object. Later, if we report an error from the same function, the destructor of the temporary object will forward all of its contents to `<<put,put>>` by rvalue reference.
But this behavior is incorrect for capturing `errno`. Consider:
[source,c++]
----
error read_file( FILE & f ) {
auto put = leaf::preload( ei_errno { errno } ); //incorrect
....
if( ferror(&f) )
return my_error;
}
----
The problem is that `errno` must not be captured before it is set by a failed operation. The solution is to instead pass a function to `preload`:
[source,c++]
----
error read_file( FILE & f ) {
auto put = leaf::preload( [ ] { return ei_errno { errno }; );
....
if( ferror(&f) )
return my_error;
}
----
When `preload` is passed a function, obtaining the error info object is deferred until the temporary object is being destroyed.
TIP: The header `boost/leaf/common.hpp` defines a function called `get_errno` which can be used for capturing `errno`, rather than using a lambda each time.
Description
Languages
C++
96.9%
Meson
1.5%
Python
1.2%
Shell
0.2%
Batchfile
0.1%