|
A11 (C++ runtime)
Native C++ implementation of the A11 streaming action runtime
|
Turns what a caller's callable throws into a Status, at the boundary. More...
#include <exception>#include <functional>#include <string_view>#include <utility>#include <absl/functional/any_invocable.h>#include <absl/status/status.h>Go to the source code of this file.
Namespaces | |
| namespace | a11 |
| namespace | a11::exception_guard |
| namespace | a11::exception_guard::internal |
Functions | |
| absl::Status | a11::exception_guard::internal::Raised (const std::exception &error, std::string_view what) |
| absl::Status | a11::exception_guard::internal::RaisedUnknown (std::string_view what) |
| The same, for something thrown that is not a std::exception. | |
| template<typename Callable > | |
| absl::Status | a11::exception_guard::Attempt (Callable &&callable, std::string_view what) |
Runs callable, reporting anything it throws as a Status. | |
| template<typename Result , typename... Args> | |
| std::function< Result(Args...)> | a11::exception_guard::Wrap (std::function< Result(Args...)> callable, std::string_view what) |
Wraps callable so that anything it throws becomes a failure. | |
| template<typename Result , typename... Args> | |
| absl::AnyInvocable< Result(Args...)> | a11::exception_guard::WrapOnce (absl::AnyInvocable< Result(Args...)> callable, std::string_view what) |
Wrap for a move-only callable. | |
| template<typename Result , typename... Args> | |
| absl::AnyInvocable< Result(Args...) && > | a11::exception_guard::WrapConsuming (absl::AnyInvocable< Result(Args...) && > callable, std::string_view what) |
Wrap for a callable that is consumed by its one invocation. | |
Turns what a caller's callable throws into a Status, at the boundary.
A11 is compiled -fno-exceptions (see a11_disallow_exceptions in cpp/CMakeLists.txt) and raises nothing of its own. A callable handed in by a caller is the exception: it may come from C++ built with exceptions, or from Python through pybind11, and it may throw. This is where that is dealt with, and there are exactly two shapes.
A try only protects a callable it invokes directly. An exception raised by a callable that A11 invokes from one of its own frames would have to unwind through that frame, and a -fno-exceptions frame has no cleanup information: the destructors of its locals do not run and the behaviour is undefined. So a helper of the shape
exception_guard::Attempt([&] { on_message(message); }, "on_message"); // WRONG here
protects nothing when it sits in one of A11's own translation units – the lambda belongs to that unit, and the unit has no exceptions.
Wrap instead returns a callable whose body was compiled with exceptions, so at call time the stack reads
[A11, no exceptions] -> [the wrapper, exceptions, try] -> [the callable]
and nothing unwinds through A11. Wrap once, where the callable is adopted – where on_message is stored, where a codec is registered – and every later invocation is safe without another thought. This is the shape to use for anything crossing A11's public API.
Submit<T> and Future<T>::OnReady cannot be wrapped that way: they are templates, instantiated by whoever calls them, so there is no single signature to pre-compile. For those, Attempt runs the callable in the frame of the translation unit doing the instantiating, and catches only if that unit has exceptions – which is exactly the unit whose callable might throw. Inside A11's own build it compiles to a plain call with no landing pad.
The caveat, and it is why the public API prefers Wrap: if A11 and a caller instantiate the same template with the same types, the linker keeps one of the two bodies, and it may keep A11's. A callable handed to a template entry point should therefore not rely on being caught. Anything type-erased – OnMessage, an action handler, a registered codec – is wrapped and carries the guarantee.
Wrap is declared here and defined in a11/internal/exception_guard_impl.h, which only the per-library boundary translation units include – a11/data/boundary.cc and its siblings, each named in the exception policy block of cpp/CMakeLists.txt. Instantiating a signature there is what makes it usable; forgetting to is a link error naming the exact signature, which is the intended way to find out. The boundary unit belongs to the library that owns the callable's type, so none of this inverts the dependency graph.