// would like to `throw make_error<...>)()` instead of returning.
// returning allows for the compile-time verification of future's
// AllowedErrorsV and also avoid the burden of throwing.
-template <_impl::ct_error ErrorV> struct unthrowable_wrapper {
+template <_impl::ct_error ErrorV>
+struct unthrowable_wrapper {
+ using wrapped_type = decltype(ErrorV);
+
unthrowable_wrapper(const unthrowable_wrapper&) = delete;
static constexpr unthrowable_wrapper instance{};
template <class T> friend const T& make_error();
}
private:
- // can be used only for initialize the `instance` member
+ // can be used only to initialize the `instance` member
explicit unthrowable_wrapper() = default;
};
template <class... ValuesT>
class future : private seastar::future<ValuesT...> {
using base_t = seastar::future<ValuesT...>;
+ using errorator_type = ceph::errorator<WrappedAllowedErrorsT...>;
// TODO: let `exception` use other type than `ct_error`.
template <_impl::ct_error V>
}
};
+ // we need the friendship for the sake of `get_exception() &&` when
+ // `safe_then()` is going to return an errorated future as a result of
+ // chaining. In contrast to `seastar::future`, errorator<T...>::future`
+ // has this member private.
+ template <class ErrorVisitor, class Futurator>
+ friend class maybe_handle_error_t;
+
+ template <class, class = std::void_t<>>
+ struct is_error {
+ static constexpr bool value = false;
+ };
+ template <class T>
+ struct is_error<T, std::void_t<typename T::wrapped_type>> {
+ // specialization for _impl::ct_error. it could be written in much
+ // simpler form – without void_t and is_same_v.
+ static constexpr bool value = \
+ std::is_same_v<typename T::wrapped_type, _impl::ct_error>;
+ };
+ template <class T>
+ static inline constexpr bool is_error_v = is_error<T>::value;
+
+ template <class, class = std::void_t<>>
+ struct get_errorator {
+ // generic template for non-errorated things (plain types and
+ // vanilla seastar::future as well).
+ using type = errorator<>;
+ };
+ template <class FutureT>
+ struct get_errorator<FutureT,
+ std::void_t<typename FutureT::errorator_type>> {
+ using type = typename FutureT::errorator_type;
+ };
+ template <class T>
+ using get_errorator_t = typename get_errorator<T>::type;
+
+ template <class ValueFuncErroratorT, class... ErrorVisitorRetsT>
+ struct make_errorator {
+ // NOP. The generic template.
+ };
+ template <class... ValueFuncWrappedAllowedErrorsT,
+ class ErrorVisitorRetsHeadT,
+ class... ErrorVisitorRetsTailT>
+ struct make_errorator<errorator<ValueFuncWrappedAllowedErrorsT...>,
+ ErrorVisitorRetsHeadT,
+ ErrorVisitorRetsTailT...> {
+ using type = std::conditional_t<
+ is_error_v<ErrorVisitorRetsHeadT>,
+ typename make_errorator<errorator<ValueFuncWrappedAllowedErrorsT...,
+ ErrorVisitorRetsHeadT>,
+ ErrorVisitorRetsTailT...>::type,
+ typename make_errorator<errorator<ValueFuncWrappedAllowedErrorsT...>,
+ ErrorVisitorRetsTailT...>::type>;
+ };
+ // finish the recursion
+ template <class... ValueFuncWrappedAllowedErrorsT>
+ struct make_errorator<errorator<ValueFuncWrappedAllowedErrorsT...>> {
+ using type = ::ceph::errorator<ValueFuncWrappedAllowedErrorsT...>;
+ };
+ template <class... Args>
+ using make_errorator_t = typename make_errorator<Args...>::type;
+
using base_t::base_t;
[[gnu::always_inline]]
template <class ValueFuncT, class ErrorVisitorT>
auto safe_then(ValueFuncT&& valfunc, ErrorVisitorT&& errfunc) {
- return this->then_wrapped(
+ using value_func_result_t = std::invoke_result_t<ValueFuncT, ValuesT&&...>;
+ // recognize whether there can be any error coming from the Value
+ // Function.
+ using value_func_errorator_t = get_errorator_t<value_func_result_t>;
+ // mutate the Value Function's errorator to harvest errors coming
+ // from the Error Visitor. Yes, it's perfectly fine to fail error
+ // handling at one step and delegate even broader set of issues
+ // to next continuation.
+ using return_errorator_t = make_errorator_t<
+ value_func_errorator_t,
+ std::decay_t<std::invoke_result_t<
+ ErrorVisitorT, decltype(WrappedAllowedErrorsT::instance)>>...>;
+ // OK, now we know about all errors next continuation must take
+ // care about. If Visitor handled everything and the Value Func
+ // doesn't return any, we'll finish with errorator<>::future
+ // which is just vanilla seastar::future – that's it, next cont
+ // finally could use `.then()`!
+ using futurator_t = \
+ typename return_errorator_t::template futurize<value_func_result_t>;
+ // `seastar::futurize`, used internally by `then_wrapped()`, wraps
+ // any non-`seastar::future` in `seastar::future`. We really don't
+ // want to end with e.g. `seastar::future<errorator::future<...>>`,
+ // so the lambda converts errorated future into plain one and here
+ // we're converting it back. As there is absolutely no difference
+ // between these types from the memory layout's POV (casting could
+ // be used instead), I count on compiler's ability to elide copies.
+ return typename futurator_t::type{ this->then_wrapped(
[ valfunc = std::forward<ValueFuncT>(valfunc),
errfunc = std::forward<ErrorVisitorT>(errfunc)
] (auto future) mutable {
- using futurator_t = \
- seastar::futurize<std::result_of_t<ValueFuncT(ValuesT&&...)>>;
if (__builtin_expect(future.failed(), false)) {
maybe_handle_error_t<ErrorVisitorT, futurator_t> maybe_handle_error(
std::forward<ErrorVisitorT>(errfunc),
std::move(future).get_exception()
);
(maybe_handle_error(WrappedAllowedErrorsT::instance) , ...);
- return std::move(maybe_handle_error).get_result();
+ return plainify(std::move(maybe_handle_error).get_result());
} else {
- return futurator_t::apply(std::forward<ValueFuncT>(valfunc),
- std::move(future).get());
+ return plainify(futurator_t::apply(std::forward<ValueFuncT>(valfunc),
+ std::move(future).get()));
}
- });
+ })};
}
template <class Func>
void then(Func&&) = delete;
- friend errorator<WrappedAllowedErrorsT...>;
+
+ private:
+ // for the sake of `plainify()` let any errorator convert errorated
+ // future into plain one.
+ template <class... AnyWrappedAllowedErrorsT>
+ friend class errorator;
+
+ base_t&& as_plain_future() && {
+ return std::move(*this);
+ }
};
- template <class... ValuesT>
- static future<ValuesT...> its_error_free(seastar::future<ValuesT...>&& plain_future) {
- return future<ValuesT...>(std::move(plain_future));
+ // the visitor that forwards handling of all errors to next continuation
+ struct pass_further {
+ template <_impl::ct_error ErrorV>
+ const auto& operator()(const unthrowable_wrapper<ErrorV>& e) {
+ static_assert((... || (e == WrappedAllowedErrorsT::instance)),
+ "passing further disallowed ct_error");
+ return ::crimson::make_error<std::decay_t<decltype(e)>>();
+ }
+ };
+
+private:
+ template <class... Args>
+ static decltype(auto) plainify(seastar::future<Args...>&& fut) {
+ return std::forward<seastar::future<Args...>>(fut);
}
-}; // class errorator
+ template <class Arg>
+ static decltype(auto) plainify(Arg&& arg) {
+ return std::forward<Arg>(arg).as_plain_future();
+ }
+
+ template <class T, class = std::void_t<T>>
+ class futurize {
+ using vanilla_futurize = seastar::futurize<T>;
+
+ template <class...>
+ struct tuple2future {};
+ template <class... Args>
+ struct tuple2future <std::tuple<Args...>> {
+ using type = future<Args...>;
+ };
+
+ public:
+ using type =
+ typename tuple2future<typename vanilla_futurize::value_type>::type;
+
+ template <class Func, class... Args>
+ static type apply(Func&& func, std::tuple<Args...>&& args) {
+ return vanilla_futurize::apply(std::forward<Func>(func),
+ std::forward<std::tuple<Args...>>(args));
+ }
+
+ template <typename Arg>
+ static type make_exception_future(Arg&& arg) {
+ return vanilla_futurize::make_exception_future(std::forward<Arg>(arg));
+ }
+ };
+ template <template <class...> class ErroratedFutureT,
+ class... ValuesT>
+ class futurize<ErroratedFutureT<ValuesT...>,
+ std::void_t<
+ typename ErroratedFutureT<ValuesT...>::errorator_type>> {
+ public:
+ using type = ::ceph::errorator<WrappedAllowedErrorsT...>::future<ValuesT...>;
+
+ template <class Func, class... Args>
+ static type apply(Func&& func, std::tuple<Args...>&& args) {
+ try {
+ return ::seastar::apply(std::forward<Func>(func),
+ std::forward<std::tuple<Args...>>(args));
+ } catch (...) {
+ return make_exception_future(std::current_exception());
+ }
+ }
+
+ template <typename Arg>
+ static type make_exception_future(Arg&& arg) {
+ return ::seastar::make_exception_future<ValuesT...>(std::forward<Arg>(arg));
+ }
+ };
+
+ // needed because of return_errorator_t::template futurize<...>
+ template <class... ValueT>
+ friend class future;
+}; // class errorator, generic template
+
+// no errors? errorator<>::future is plain seastar::future then!
+template <>
+class errorator<> {
+public:
+ template <class... ValuesT>
+ using future = ::seastar::future<ValuesT...>;
+
+ template <class T>
+ using futurize = ::seastar::futurize<T>;
+}; // class errorator, <> specialization
namespace ct_error {
using enoent = unthrowable_wrapper<_impl::ct_error::enoent>;