The `result_try` and `result_futurize_try` are supposed to handle both
failed results and exceptions in a way similar to a try..catch block.
In order to catch exceptions, the metaprogramming machinery invokes the
fallible code inside a stack of try..catch blocks, each one of them
handling one exception. This is done instead of creating a single
try..catch block, as to my knowledge it is not possible to create
a try..catch block with the number of "catch" clauses depending on a
variadic template parameter pack.
Unfortunately, a "try" with multiple "catches" is not functionally
equivalent to a "try block stack". Consider the following code:
try {
try {
return execute_try_block();
} catch (const derived_exception&) {
// 1
}
} catch (const base_exception&) {
// 2
}
If `execute_try_block` throws `derived_exception` and the (1) catch
handler rethrows this exception, it will also be handled in (2), which
is not the same behavior as if the try..catch stack was "flat".
This causes wrong behavior in `result_try` and `result_futurize_try`.
The following snippet has the same, wrong behavior as the previous one:
return utils::result_try([&] {
return execute_try_block();
}, utils::result_catch<derived_exception>([&] (const auto&& ex) {
// 1
}), utils::result_catch<base_exception>([&] (const auto&& ex) {
// 2
});
This commit fixes the problem by adding a boolean flag which is set just
before a catch handler is executed. If another catch handler is
accidentally matched due to exception rethrow, the catch handler is
skipped and exception is automatically rethrown.
Tests: unit(dev, debug)
Fixes: #10211
Closes #10216
623 lines
24 KiB
C++
623 lines
24 KiB
C++
/*
|
|
* Copyright 2022-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <concepts>
|
|
#include <type_traits>
|
|
#include "utils/result.hh"
|
|
|
|
namespace utils {
|
|
|
|
namespace internal {
|
|
|
|
struct noop_converter {
|
|
template<typename T>
|
|
using wrapped_type = T;
|
|
|
|
template<typename T>
|
|
static
|
|
wrapped_type<T>
|
|
wrap(T&& t) {
|
|
return std::move(t);
|
|
}
|
|
|
|
template<typename F, typename... Args>
|
|
static
|
|
wrapped_type<std::invoke_result_t<F, Args...>>
|
|
invoke(F&& f, Args&&... args) {
|
|
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
|
|
}
|
|
};
|
|
|
|
struct futurizing_converter {
|
|
template<typename T>
|
|
using wrapped_type = seastar::futurize_t<T>;
|
|
|
|
template<typename T>
|
|
static
|
|
wrapped_type<T>
|
|
wrap(T&& t) {
|
|
if constexpr (seastar::is_future<T>::value) {
|
|
return t;
|
|
} else {
|
|
return seastar::make_ready_future<T>(std::forward<T>(t));
|
|
}
|
|
}
|
|
|
|
template<typename F, typename... Args>
|
|
static
|
|
wrapped_type<std::invoke_result_t<F, Args...>>
|
|
invoke(F&& f, Args&&... args) {
|
|
return seastar::futurize_invoke(std::forward<F>(f), std::forward<Args>(args)...);
|
|
}
|
|
};
|
|
|
|
template<typename From, typename Converter, typename To>
|
|
concept ConvertsWithTo = std::convertible_to<typename Converter::template wrapped_type<From>, typename Converter::template wrapped_type<To>>;
|
|
|
|
// We require forall<ExceptionContainerResult R> ExceptionHandle<H, R>.
|
|
// However, C++ does not support quantification like that in the constraints.
|
|
// Here, we use the dummy_result to assert that the handles defined below
|
|
// at least work with dummy_result.
|
|
template<typename T = void>
|
|
using dummy_result = result_with_exception<T, std::exception>;
|
|
|
|
// TODO: Should we make a virtual base class for this?
|
|
// This would allow using non-generic lambdas in result_catch and result_catch_dots.
|
|
template<typename Handle>
|
|
concept ExceptionHandle =
|
|
ExceptionContainerResult<dummy_result<>> &&
|
|
requires (Handle handle, seastar::promise<dummy_result<int>>& p) {
|
|
{ handle.into_result() } -> std::same_as<dummy_result<>>;
|
|
{ handle.into_future() } -> std::same_as<seastar::future<dummy_result<>>>;
|
|
{ handle.forward_to_promise(p) } -> std::same_as<void>;
|
|
};
|
|
|
|
template<ExceptionContainerResult R>
|
|
struct failed_result_handle {
|
|
private:
|
|
R&& _failed_result;
|
|
|
|
public:
|
|
failed_result_handle(R&& r) : _failed_result(std::move(r)) {}
|
|
|
|
// Returns the failed result. Can be used only once as this moves out
|
|
// the failed result. This should be called once during the handler's
|
|
// lifetime, preferably as `return handler.into_result()`.
|
|
R into_result() {
|
|
return std::move(_failed_result);
|
|
}
|
|
|
|
seastar::future<R> into_future() {
|
|
return seastar::make_ready_future<R>(std::move(_failed_result));
|
|
}
|
|
|
|
template<ExceptionContainerResult S>
|
|
requires std::same_as<typename R::error_type, typename S::error_type>
|
|
void forward_to_promise(seastar::promise<S>& p) {
|
|
p.set_value(std::move(_failed_result).as_failure());
|
|
}
|
|
|
|
const typename R::error_type& as_inner() const & {
|
|
return _failed_result.assume_error();
|
|
}
|
|
|
|
typename R::error_type&& as_inner() && {
|
|
return std::move(_failed_result).assume_error();
|
|
}
|
|
|
|
typename R::error_type clone_inner() {
|
|
return _failed_result.assume_error().clone();
|
|
}
|
|
};
|
|
|
|
static_assert(ExceptionHandle<failed_result_handle<dummy_result<>>>);
|
|
|
|
template<ExceptionContainerResult R>
|
|
struct exception_ptr_handle {
|
|
private:
|
|
std::exception_ptr _eptr;
|
|
|
|
public:
|
|
exception_ptr_handle(std::exception_ptr&& eptr) : _eptr(std::move(eptr)) {}
|
|
|
|
// Throws the exception.
|
|
[[noreturn]] R into_result() {
|
|
std::rethrow_exception(std::move(_eptr));
|
|
}
|
|
|
|
seastar::future<R> into_future() {
|
|
return seastar::make_exception_future<R>(std::move(_eptr));
|
|
}
|
|
|
|
template<ExceptionContainerResult S>
|
|
requires std::same_as<typename R::error_type, typename S::error_type>
|
|
void forward_to_promise(seastar::promise<S>& p) {
|
|
p.set_exception(std::move(_eptr));
|
|
}
|
|
|
|
const std::exception_ptr& as_inner() const & {
|
|
return _eptr;
|
|
}
|
|
|
|
std::exception_ptr&& as_inner() && {
|
|
return std::move(_eptr);
|
|
}
|
|
|
|
std::exception_ptr clone_inner() {
|
|
return _eptr;
|
|
}
|
|
};
|
|
|
|
// Check that it is a valid handle with some arbitrary ExceptionContainerResult
|
|
static_assert(ExceptionHandle<exception_ptr_handle<dummy_result<>>>);
|
|
|
|
template<typename Ex, typename Cb>
|
|
struct result_catcher {
|
|
private:
|
|
Cb _cb;
|
|
|
|
public:
|
|
using exception_type = Ex;
|
|
using callback_type = Cb;
|
|
|
|
public:
|
|
result_catcher(Cb&& cb) : _cb(std::move(cb)) {}
|
|
|
|
template<ExceptionContainerResult R, typename Converter, typename Continuation>
|
|
typename Converter::template wrapped_type<R>
|
|
handle_exception_from_result(const auto& ex, R& original_result, Continuation&& cont) {
|
|
if constexpr (std::is_base_of_v<Ex, std::remove_cvref_t<decltype(ex)>>) {
|
|
if constexpr (std::is_invocable_v<Cb, const Ex&, failed_result_handle<R>>) {
|
|
// Invoke with exception reference and handle
|
|
return Converter::template invoke(_cb, ex, failed_result_handle<R>(std::move(original_result)));
|
|
} else {
|
|
// Simplified interface - invoke with reference to exception only
|
|
static_assert(std::is_invocable_v<Cb, const Ex&>,
|
|
"The handler function does not have a suitable call operator");
|
|
return Converter::template invoke(_cb, ex);
|
|
}
|
|
} else {
|
|
// Let another handler try it
|
|
return std::invoke(std::forward<Continuation>(cont));
|
|
}
|
|
}
|
|
|
|
template<ExceptionContainerResult R, typename Converter, typename Continuation>
|
|
auto wrap_in_catch(Continuation&& cont) {
|
|
return [this, cont = std::forward<Continuation>(cont)] (bool& already_caught) mutable -> typename Converter::template wrapped_type<R> {
|
|
try {
|
|
return std::invoke(std::forward<Continuation>(cont), already_caught);
|
|
} catch (const Ex& ex) {
|
|
if (already_caught) {
|
|
throw;
|
|
}
|
|
already_caught = true;
|
|
|
|
if constexpr (std::is_invocable_v<Cb, const Ex&, exception_ptr_handle<R>>) {
|
|
// Invoke with exception reference and handle
|
|
return Converter::template invoke(_cb, ex, exception_ptr_handle<R>(std::current_exception()));
|
|
} else {
|
|
// Simplified interface - invoke with reference to exception only
|
|
static_assert(std::is_invocable_v<Cb, const Ex&>,
|
|
"The handler function does not have a suitable call operator");
|
|
return Converter::template invoke(_cb, ex);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
struct is_result_catcher : std::false_type {};
|
|
|
|
template<typename Ex, typename Cb>
|
|
struct is_result_catcher<result_catcher<Ex, Cb>> : std::true_type {};
|
|
|
|
template<typename Handler, typename Converter, typename Result>
|
|
concept ResultCatcherCallback =
|
|
requires (typename Handler::callback_type callback, typename Handler::exception_type& ex,
|
|
failed_result_handle<Result>&& frh, exception_ptr_handle<Result>&& eph) {
|
|
{ callback(ex, frh) } -> ConvertsWithTo<Converter, Result>;
|
|
{ callback(ex, eph) } -> ConvertsWithTo<Converter, Result>;
|
|
} ||
|
|
requires (typename Handler::callback_type callback, typename Handler::exception_type& ex) {
|
|
{ callback(ex) } -> ConvertsWithTo<Converter, Result>;
|
|
};
|
|
|
|
template<typename Handler, typename Converter, typename Result>
|
|
concept ResultCatcher =
|
|
is_result_catcher<Handler>::value &&
|
|
ResultCatcherCallback<Handler, Converter, Result>;
|
|
|
|
template<typename Cb>
|
|
struct result_catcher_dots {
|
|
private:
|
|
Cb _cb;
|
|
|
|
public:
|
|
using callback_type = Cb;
|
|
|
|
public:
|
|
result_catcher_dots(Cb&& cb) : _cb(std::move(cb)) {}
|
|
|
|
template<ExceptionContainerResult R, typename Converter, typename Continuation>
|
|
typename Converter::template wrapped_type<R>
|
|
handle_exception_from_result(const auto& ex, R& original_result, Continuation&& cont) {
|
|
if constexpr (std::is_invocable_v<Cb, failed_result_handle<R>>) {
|
|
// Invoke with handle
|
|
return Converter::template invoke(_cb, failed_result_handle<R>(std::move(original_result)));
|
|
} else {
|
|
// Simplified interface - invoke without arguments
|
|
static_assert(std::is_invocable_v<Cb>,
|
|
"The handler function does not have a suitable call operator");
|
|
return Converter::template invoke(_cb);
|
|
}
|
|
// Don't propagate to the next handler. The catch (...) is supposed
|
|
// to match on all errors.
|
|
}
|
|
|
|
template<ExceptionContainerResult R, typename Converter, typename Continuation>
|
|
auto wrap_in_catch(Continuation&& cont) {
|
|
return [this, cont = std::forward<Continuation>(cont)] (bool& already_caught) mutable -> typename Converter::template wrapped_type<R> {
|
|
try {
|
|
return std::invoke(std::forward<Continuation>(cont), already_caught);
|
|
} catch (...) {
|
|
if (already_caught) {
|
|
throw;
|
|
}
|
|
already_caught = true;
|
|
|
|
if constexpr (std::is_invocable_v<Cb, exception_ptr_handle<R>>) {
|
|
// Invoke with handle
|
|
return Converter::template invoke(_cb, exception_ptr_handle<R>(std::current_exception()));
|
|
} else {
|
|
// Simplified interface - invoke without arguments
|
|
static_assert(std::is_invocable_v<Cb>,
|
|
"The handler function does not have a suitable call operator");
|
|
return Converter::template invoke(_cb);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
struct is_result_catcher_dots : std::false_type {};
|
|
|
|
template<typename Cb>
|
|
struct is_result_catcher_dots<result_catcher_dots<Cb>> : std::true_type {};
|
|
|
|
template<typename Handler, typename Converter, typename Result>
|
|
concept DotsResultCatcherCallback =
|
|
requires (typename Handler::callback_type callback,
|
|
failed_result_handle<Result>&& frh, exception_ptr_handle<Result>&& eph) {
|
|
{ callback(frh) } -> ConvertsWithTo<Converter, Result>;
|
|
{ callback(eph) } -> ConvertsWithTo<Converter, Result>;
|
|
} ||
|
|
requires (typename Handler::callback_type callback) {
|
|
{ callback() } -> ConvertsWithTo<Converter, Result>;
|
|
};
|
|
|
|
template<typename Handler, typename Converter, typename Result>
|
|
concept DotsResultCatcher =
|
|
is_result_catcher_dots<Handler>::value &&
|
|
DotsResultCatcherCallback<Handler, Converter, Result>;
|
|
|
|
// Constructs an `invoke_in_try_catch` function which allows to call a callback
|
|
// and handle C++ exceptions using a set of handlers given during construction.
|
|
//
|
|
// The `Converter` is used to appropriately invoke the exception handlers.
|
|
template<ExceptionContainerResult R, typename Converter, typename... CatchHandlers>
|
|
struct try_catch_chain_impl {};
|
|
|
|
template<ExceptionContainerResult R, typename Converter, typename FirstCatchHandler, typename... CatchHandlers>
|
|
struct try_catch_chain_impl<R, Converter, FirstCatchHandler, CatchHandlers...> {
|
|
template<typename Continuation>
|
|
static
|
|
typename Converter::template wrapped_type<R>
|
|
invoke_in_try_catch(Continuation&& cont, FirstCatchHandler& first_handler, CatchHandlers&... catch_handlers) {
|
|
return try_catch_chain_impl<R, Converter, CatchHandlers...>::template invoke_in_try_catch<>(
|
|
first_handler.template wrap_in_catch<R, Converter, Continuation>(std::forward<Continuation>(cont)),
|
|
catch_handlers...);
|
|
}
|
|
};
|
|
|
|
template<ExceptionContainerResult R, typename Converter>
|
|
struct try_catch_chain_impl<R, Converter> {
|
|
template<typename Continuation>
|
|
static
|
|
typename Converter::template wrapped_type<R>
|
|
invoke_in_try_catch(Continuation&& cont) {
|
|
// Not using an invoker as we always want it to throw
|
|
bool already_caught = false;
|
|
return std::invoke(std::forward<Continuation>(cont), already_caught);
|
|
}
|
|
};
|
|
|
|
// Given a set of handlers, constructs a visitor which can be used to inspect
|
|
// a failed result.
|
|
//
|
|
// The `Converter` is used to appropriately invoke the exception handlers
|
|
// and convert the result to the return type if no handlers match on it.
|
|
template<ExceptionContainerResult R, typename Converter, typename... ResultHandlers>
|
|
struct combined_handler_impl {};
|
|
|
|
template<ExceptionContainerResult R, typename Converter, typename FirstResultHandler, typename... ResultHandlers>
|
|
struct combined_handler_impl<R, Converter, FirstResultHandler, ResultHandlers...>
|
|
: protected combined_handler_impl<R, Converter, ResultHandlers...> {
|
|
private:
|
|
using base = combined_handler_impl<R, Converter, ResultHandlers...>;
|
|
FirstResultHandler& _first_handler;
|
|
|
|
public:
|
|
combined_handler_impl(FirstResultHandler& first_handler, ResultHandlers&... result_handlers)
|
|
: base(result_handlers...)
|
|
, _first_handler(first_handler)
|
|
{ }
|
|
|
|
typename Converter::template wrapped_type<R>
|
|
handle(const auto& ex, R& original_result) {
|
|
return _first_handler.template handle_exception_from_result<R, Converter>(
|
|
ex, original_result,
|
|
[this, &ex, &original_result] () mutable {
|
|
return base::handle(ex, original_result);
|
|
});
|
|
}
|
|
|
|
auto with_original_result(R& res) {
|
|
return [this, &res] (const auto& ex) {
|
|
return handle(ex, res);
|
|
};
|
|
}
|
|
};
|
|
|
|
template<ExceptionContainerResult R, typename Converter>
|
|
struct combined_handler_impl<R, Converter> {
|
|
public:
|
|
typename Converter::template wrapped_type<R>
|
|
handle(const auto& ex, R& original_result) {
|
|
// No more handlers to try out, just return
|
|
return Converter::template wrap(std::move(original_result));
|
|
}
|
|
|
|
auto with_original_result(R& res) {
|
|
return [this, &res] (const auto& ex) {
|
|
return handle(ex, res);
|
|
};
|
|
}
|
|
};
|
|
|
|
template<typename Result, typename... Handlers>
|
|
[[gnu::always_inline]]
|
|
inline
|
|
seastar::future<Result>
|
|
result_handle_available_future(seastar::future<Result>&& f, Handlers&... handlers) {
|
|
using combined_handler_type = internal::combined_handler_impl<Result, internal::futurizing_converter, Handlers...>;
|
|
using try_catch_chain_type = internal::try_catch_chain_impl<Result, internal::futurizing_converter, Handlers...>;
|
|
|
|
if (!f.failed()) {
|
|
Result&& res = f.get();
|
|
if (res) {
|
|
return seastar::make_ready_future<Result>(std::move(res));
|
|
}
|
|
|
|
// Handle the exception in result
|
|
auto combined_handler = combined_handler_type(handlers...);
|
|
return res.assume_error().accept(combined_handler.with_original_result(res));
|
|
} else {
|
|
// The future has an exception
|
|
// We need to create a try..catch chain from the handlers
|
|
// and rethrow the exception inside it
|
|
//
|
|
// futurize_invoke will protect us in case the exception is not handled
|
|
// by any of the provided handlers.
|
|
return seastar::futurize_invoke([&] {
|
|
return try_catch_chain_type::invoke_in_try_catch(
|
|
[&f] (bool&) mutable -> seastar::future<Result> { std::rethrow_exception(std::move(f).get_exception()); },
|
|
handlers...);
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
template<typename Handler, typename Converter, typename Result>
|
|
concept ResultCatcherMaybeDots =
|
|
internal::ResultCatcher<Handler, Converter, Result> ||
|
|
internal::DotsResultCatcher<Handler, Converter, Result>;
|
|
|
|
template<typename Fun>
|
|
concept ResultTryBody = requires (Fun fun) {
|
|
{ fun() } -> ExceptionContainerResult<>;
|
|
} && std::is_nothrow_move_constructible_v<std::invoke_result_t<Fun>>;
|
|
|
|
template<typename Fun>
|
|
concept ResultFuturizeTryBody = requires (Fun&& fun) {
|
|
{ seastar::futurize_invoke(std::forward<Fun>(fun)) } -> ExceptionContainerResultFuture<>;
|
|
} && std::is_nothrow_move_constructible_v<typename seastar::futurize_t<std::invoke_result_t<Fun>>::value_type>;
|
|
|
|
/// \brief Allows to handle C++ exceptions and failed results in a unified way.
|
|
///
|
|
/// When you modify a code path to return a result<> and you encounter
|
|
/// a try..catch block, you can use it to migrate the block so that it handles
|
|
/// both C++ exceptions and exceptions stored in the result<>.
|
|
///
|
|
/// \section Example
|
|
///
|
|
/// Let's say that you have the following try..catch chain:
|
|
///
|
|
/// try {
|
|
/// return a_function_that_may_throw();
|
|
/// } catch (const my_exception& ex) {
|
|
/// return 123;
|
|
/// } catch (...) {
|
|
/// throw;
|
|
/// }
|
|
///
|
|
/// You can add support for results in the following way:
|
|
///
|
|
/// return utils::result_try([&] {
|
|
/// return a_function_that_may_throw_or_return_a_failed_result();
|
|
/// }, utils::result_catch<my_exception>([&] (const Ex&) -> result<int> {
|
|
/// return 123;
|
|
/// }), utils::result_catch_dots([&] (auto&& handle) -> result<int> {
|
|
/// return handle.into_result();
|
|
/// });
|
|
///
|
|
/// Each `result_catch` handler declares the exception type it handles
|
|
/// and accepts a const reference to it as the first argument.
|
|
/// The `result_catch_dots` handler is equivalent to `(...)` so it does not
|
|
/// declare exception type or accept a reference to it.
|
|
///
|
|
/// In addition, each `result_catch` and `result_catch_dots` can optionally
|
|
/// accept a reference to an exception handle. The exception handle can be used
|
|
/// to return/rethrow the exception being currently handled. Depending on
|
|
/// whether the handler is invoked for a C++ exception or a failed result<>,
|
|
/// it will have a different type and its `into_result()` method will either
|
|
/// rethrow the exception or return the failed result, respectively.
|
|
///
|
|
/// \section Limitations
|
|
///
|
|
/// The main limitation of result_try is that is is not as flexible with control
|
|
/// flow as a try..catch block is. If you have some logic after the try..catch
|
|
/// block and some code paths in it block return and some don't, you need to
|
|
/// mark it somehow in the return value - for example, you can change result<T>
|
|
/// to result<std::optional<T>> and continue if it is std::nullopt, otherwise
|
|
/// return.
|
|
///
|
|
/// The return type of the first argument must be nothrow move constructible.
|
|
/// Because this function uses many recursive calls to simulate a multi-catch
|
|
/// clause try..catch block and NRVO is not in general guaranteed, there is
|
|
/// a risk that some moves are not elided and one of them can throw
|
|
/// in the middle of the stack. This exception can be thrown after we already
|
|
/// exited the try..catch blocks for some handlers, so not all of them will
|
|
/// participate in handling this exception. Because of the inability to properly
|
|
/// handle exceptions from failed moves, hence the nothrow-move-constructibility
|
|
/// requirement.
|
|
///
|
|
/// This function does not work with futures and will trigger a static_assert
|
|
/// if you use it with a future-returning function. See result_futurize_try
|
|
/// for a version which additionally works with exceptional futures.
|
|
template<ResultTryBody Fun, ResultCatcherMaybeDots<internal::noop_converter, std::invoke_result_t<Fun>>... Handlers>
|
|
requires (!seastar::is_future<std::invoke_result_t<Fun>>::value)
|
|
inline
|
|
std::invoke_result_t<Fun>
|
|
result_try(Fun&& fun, Handlers&&... handlers) {
|
|
using result_type = std::invoke_result_t<Fun>;
|
|
using combined_handler_type = internal::combined_handler_impl<result_type, internal::noop_converter, Handlers...>;
|
|
using try_catch_chain_type = internal::try_catch_chain_impl<result_type, internal::noop_converter, Handlers...>;
|
|
|
|
// Invoke `fun` and catch C++ exceptions if any occur
|
|
result_type res = try_catch_chain_type::template invoke_in_try_catch<>([&fun] (bool&) { return fun(); }, handlers...);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
|
|
// No C++ exceptions but the result is a failure - inspect using a visitor
|
|
auto combined_handler = combined_handler_type(handlers...);
|
|
return res.assume_error().accept(combined_handler.with_original_result(res));
|
|
}
|
|
|
|
/// \brief A version of `result_try` which works with futures. It handles
|
|
/// C++ exceptions, failed results and exceptional futures returned from `fun`.
|
|
///
|
|
/// Migration from a try..catch block or f.handle_exception(...) is similar
|
|
/// as in the case of `result_try`, with a small number of differences
|
|
/// described below.
|
|
///
|
|
/// The `fun` function and all exception handlers are futurize_invoked,
|
|
/// therefore all C++ exceptions are converted to exceptional futures.
|
|
///
|
|
/// In order to perform `throw`/`make_exception_future<>(std::current_exception())`,
|
|
/// you should prefer `handle.into_future()` instead of `handle.into_result()`.
|
|
/// Unlike the latter, the former avoids rethrowing the exception and just
|
|
/// returns an exceptional future from a captured exception pointer.
|
|
///
|
|
/// Important note about preemption: if the `try` body returns a failure
|
|
/// without preemption, then the `catch` handler will be called without
|
|
/// preemption, too.
|
|
template<ResultFuturizeTryBody Fun,
|
|
ResultCatcherMaybeDots<internal::futurizing_converter,
|
|
typename seastar::futurize_t<std::invoke_result_t<Fun>>::value_type>... Handlers>
|
|
seastar::futurize_t<std::invoke_result_t<Fun>>
|
|
result_futurize_try(Fun&& fun, Handlers&&... handlers) {
|
|
using fun_return_type = std::invoke_result_t<Fun>;
|
|
using future_type = seastar::futurize_t<fun_return_type>;
|
|
using result_type = typename future_type::value_type;
|
|
|
|
using try_catch_chain_type = internal::try_catch_chain_impl<result_type, internal::futurizing_converter, Handlers...>;
|
|
|
|
// Try to invoke the function and handle C++ exceptions if there are any
|
|
bool no_exceptions = false;
|
|
future_type f = seastar::futurize_invoke([&] () {
|
|
return try_catch_chain_type::template invoke_in_try_catch<>([&no_exceptions, &fun] (bool&) {
|
|
// If `fun` didn't throw, we can set the `no_exceptions` flag
|
|
// so that we know that there was a throw and we shouldn't
|
|
// run the exception handling logic again.
|
|
fun_return_type ret = fun();
|
|
no_exceptions = true;
|
|
return seastar::futurize<fun_return_type>::convert(std::move(ret));
|
|
}, handlers...);
|
|
});
|
|
|
|
if (!__builtin_expect(no_exceptions, true)) {
|
|
// There were C++ exceptions, and we handled them already
|
|
return f;
|
|
}
|
|
|
|
// Although then_wrapped should call the lambda without preemption,
|
|
// this is not guaranteed in debug mode, and I feel that the compiler
|
|
// struggles to generate efficient code for this case in release mode.
|
|
// We manually check if the future is available so that we can ensure
|
|
// the guarantee stated in the comment.
|
|
if (f.available()) {
|
|
return internal::result_handle_available_future(std::move(f), handlers...);
|
|
}
|
|
return f.then_wrapped([...handlers = std::move(handlers)] (future_type&& f) mutable -> future_type {
|
|
return internal::result_handle_available_future(std::move(f), handlers...);
|
|
});
|
|
}
|
|
|
|
/// \brief Represents a `catch (const Ex& ex) {}` part in a `result_try` chain.
|
|
///
|
|
/// The callback must be a generic lambda, and be callable with one
|
|
/// of the following sets of arguments:
|
|
///
|
|
/// (const Ex&)
|
|
/// (const Ex&, Handle&&)
|
|
///
|
|
/// where `Handle` is satisfies the ExceptionHandle<R> concept
|
|
/// and `R` is the result return type.
|
|
///
|
|
/// See the description of `result_try` for more info.
|
|
template<typename Ex, typename Cb>
|
|
auto result_catch(Cb&& cb) {
|
|
return internal::result_catcher<Ex, Cb>(std::move(cb));
|
|
}
|
|
|
|
/// \brief Represents a `catch (...) {}` part in a `result_try` chain.
|
|
///
|
|
/// The callback must be a generic lambda, and be callable with one
|
|
/// of the following sets of arguments:
|
|
///
|
|
/// ()
|
|
/// (Handle&&)
|
|
///
|
|
/// where `Handle` is satisfies the ExceptionHandle<R> concept
|
|
/// and `R` is the result return type.
|
|
///
|
|
/// See the description of `result_try` for more info.
|
|
template<typename Cb>
|
|
auto result_catch_dots(Cb&& cb) {
|
|
return internal::result_catcher_dots<Cb>(std::move(cb));
|
|
}
|
|
|
|
}
|