From 8c212763e8c24bba48ac97836efd1c65813b106b Mon Sep 17 00:00:00 2001 From: Radoslaw Zarzynski Date: Fri, 6 Sep 2019 13:10:41 +0200 Subject: [PATCH] crimson: bring compile time-verified chaining of errorators. Signed-off-by: Radoslaw Zarzynski --- src/crimson/common/errorator.h | 206 ++++++++++++++++++++++++++++++--- src/crimson/os/cyan_store.cc | 3 +- 2 files changed, 193 insertions(+), 16 deletions(-) diff --git a/src/crimson/common/errorator.h b/src/crimson/common/errorator.h index fb8214a04d5..dc20887f1cc 100644 --- a/src/crimson/common/errorator.h +++ b/src/crimson/common/errorator.h @@ -19,7 +19,10 @@ namespace _impl { // 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 friend const T& make_error(); @@ -31,7 +34,7 @@ template <_impl::ct_error ErrorV> struct unthrowable_wrapper { } private: - // can be used only for initialize the `instance` member + // can be used only to initialize the `instance` member explicit unthrowable_wrapper() = default; }; @@ -44,6 +47,7 @@ struct errorator { template class future : private seastar::future { using base_t = seastar::future; + using errorator_type = ceph::errorator; // TODO: let `exception` use other type than `ct_error`. template <_impl::ct_error V> @@ -100,6 +104,67 @@ struct errorator { } }; + // 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::future` + // has this member private. + template + friend class maybe_handle_error_t; + + template > + struct is_error { + static constexpr bool value = false; + }; + template + struct is_error> { + // 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; + }; + template + static inline constexpr bool is_error_v = is_error::value; + + template > + struct get_errorator { + // generic template for non-errorated things (plain types and + // vanilla seastar::future as well). + using type = errorator<>; + }; + template + struct get_errorator> { + using type = typename FutureT::errorator_type; + }; + template + using get_errorator_t = typename get_errorator::type; + + template + struct make_errorator { + // NOP. The generic template. + }; + template + struct make_errorator, + ErrorVisitorRetsHeadT, + ErrorVisitorRetsTailT...> { + using type = std::conditional_t< + is_error_v, + typename make_errorator, + ErrorVisitorRetsTailT...>::type, + typename make_errorator, + ErrorVisitorRetsTailT...>::type>; + }; + // finish the recursion + template + struct make_errorator> { + using type = ::ceph::errorator; + }; + template + using make_errorator_t = typename make_errorator::type; + using base_t::base_t; [[gnu::always_inline]] @@ -144,37 +209,150 @@ struct errorator { template auto safe_then(ValueFuncT&& valfunc, ErrorVisitorT&& errfunc) { - return this->then_wrapped( + using value_func_result_t = std::invoke_result_t; + // recognize whether there can be any error coming from the Value + // Function. + using value_func_errorator_t = get_errorator_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>...>; + // 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; + // `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>`, + // 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(valfunc), errfunc = std::forward(errfunc) ] (auto future) mutable { - using futurator_t = \ - seastar::futurize>; if (__builtin_expect(future.failed(), false)) { maybe_handle_error_t maybe_handle_error( std::forward(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(valfunc), - std::move(future).get()); + return plainify(futurator_t::apply(std::forward(valfunc), + std::move(future).get())); } - }); + })}; } template void then(Func&&) = delete; - friend errorator; + + private: + // for the sake of `plainify()` let any errorator convert errorated + // future into plain one. + template + friend class errorator; + + base_t&& as_plain_future() && { + return std::move(*this); + } }; - template - static future its_error_free(seastar::future&& plain_future) { - return future(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& e) { + static_assert((... || (e == WrappedAllowedErrorsT::instance)), + "passing further disallowed ct_error"); + return ::crimson::make_error>(); + } + }; + +private: + template + static decltype(auto) plainify(seastar::future&& fut) { + return std::forward>(fut); } -}; // class errorator + template + static decltype(auto) plainify(Arg&& arg) { + return std::forward(arg).as_plain_future(); + } + + template > + class futurize { + using vanilla_futurize = seastar::futurize; + + template + struct tuple2future {}; + template + struct tuple2future > { + using type = future; + }; + + public: + using type = + typename tuple2future::type; + + template + static type apply(Func&& func, std::tuple&& args) { + return vanilla_futurize::apply(std::forward(func), + std::forward>(args)); + } + + template + static type make_exception_future(Arg&& arg) { + return vanilla_futurize::make_exception_future(std::forward(arg)); + } + }; + template