// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #pragma once #include #include #include #include "include/ceph_assert.h" namespace crimson { template inline auto do_for_each(Iterator begin, Iterator end, AsyncAction action) { using futurator = \ ::seastar::futurize>; if (begin == end) { return futurator::type::errorator_type::template make_ready_future<>(); } while (true) { auto f = futurator::invoke(action, *begin); ++begin; if (begin == end) { return f; } if (!f.available() || seastar::need_preempt()) { return std::move(f)._then( [ action = std::move(action), begin = std::move(begin), end = std::move(end) ] () mutable { return ::crimson::do_for_each(std::move(begin), std::move(end), std::move(action)); }); } if (f.failed()) { return f; } } } template inline auto do_for_each(Container& c, AsyncAction action) { return ::crimson::do_for_each(std::begin(c), std::end(c), std::move(action)); } template inline auto do_until(AsyncAction action) { using errorator_t = typename ::seastar::futurize_t>::errorator_type; while (true) { auto f = ::seastar::futurize_invoke(action); if (f.failed()) { return errorator_t::template make_exception_future2<>( f.get_exception() ); } else if (f.available()) { if (auto done = f.get0()) { return errorator_t::template make_ready_future<>(); } } else { return std::move(f)._then( [action = std::move(action)] (auto &&done) mutable { if (done) { return errorator_t::template make_ready_future<>(); } return ::crimson::do_until( std::move(action)); }); } } } // define the interface between error types and errorator template class error_t { static constexpr const std::type_info& get_exception_ptr_type_info() { return ConcreteErrorT::exception_ptr_type_info(); } std::exception_ptr to_exception_ptr() const { const auto* concrete_error = static_cast(this); return concrete_error->to_exception_ptr(); } decltype(auto) static from_exception_ptr(std::exception_ptr ep) { return ConcreteErrorT::from_exception_ptr(std::move(ep)); } template friend struct errorator; template friend class maybe_handle_error_t; public: template static decltype(auto) handle(Func&& func) { return ConcreteErrorT::handle(std::forward(func)); } }; // unthrowable_wrapper ensures compilation failure when somebody // 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 struct unthrowable_wrapper : error_t> { unthrowable_wrapper(const unthrowable_wrapper&) = delete; [[nodiscard]] static const auto& make() { static constexpr unthrowable_wrapper instance{}; return instance; } template static auto handle(Func&& func) { return [ func = std::forward(func) ] (const unthrowable_wrapper&) mutable -> decltype(auto) { if constexpr (std::is_invocable_v) { return std::invoke(std::forward(func), ErrorV); } else { return std::invoke(std::forward(func)); } }; } struct pass_further { decltype(auto) operator()(const unthrowable_wrapper& e) { return e; } }; struct discard { decltype(auto) operator()(const unthrowable_wrapper&) { } }; private: // can be used only to initialize the `instance` member explicit unthrowable_wrapper() = default; // implement the errorable interface struct throwable_carrier{}; static std::exception_ptr carrier_instance; static constexpr const std::type_info& exception_ptr_type_info() { return typeid(throwable_carrier); } auto to_exception_ptr() const { // error codes don't need to instantiate `std::exception_ptr` each // time as the code is actually a part of the type itself. // `std::make_exception_ptr()` on modern enough GCCs is quite cheap // (see the Gleb Natapov's patch eradicating throw/catch there), // but using one instance per type boils down the overhead to just // ref-counting. return carrier_instance; } static const auto& from_exception_ptr(std::exception_ptr) { return make(); } friend class error_t>; }; template std::exception_ptr unthrowable_wrapper::carrier_instance = \ std::make_exception_ptr< unthrowable_wrapper::throwable_carrier>({}); template struct stateful_error_t : error_t> { template explicit stateful_error_t(Args&&... args) : ep(std::make_exception_ptr(std::forward(args)...)) { } template static auto handle(Func&& func) { static_assert(std::is_invocable_v); return [ func = std::forward(func) ] (stateful_error_t&& e) mutable -> decltype(auto) { try { std::rethrow_exception(e.ep); } catch (const ErrorT& obj) { return std::invoke(std::forward(func), obj); } ceph_abort_msg("exception type mismatch – impossible!"); }; } private: std::exception_ptr ep; explicit stateful_error_t(std::exception_ptr ep) : ep(std::move(ep)) {} static constexpr const std::type_info& exception_ptr_type_info() { return typeid(ErrorT); } auto to_exception_ptr() const { return ep; } static stateful_error_t from_exception_ptr(std::exception_ptr ep) { return stateful_error_t(std::move(ep)); } friend class error_t>; }; namespace _impl { template struct always_false : std::false_type {}; }; template class maybe_handle_error_t { const std::type_info& type_info; typename FuturatorT::type result; ErrorVisitorT errfunc; public: maybe_handle_error_t(ErrorVisitorT&& errfunc, std::exception_ptr ep) : type_info(*ep.__cxa_exception_type()), result(FuturatorT::make_exception_future(std::move(ep))), errfunc(std::forward(errfunc)) { } template void handle() { static_assert(std::is_invocable::value, "provided Error Visitor is not exhaustive"); // In C++ throwing an exception isn't the sole way to signal // error with it. This approach nicely fits cold, infrequent cases // but when applied to a hot one, it will likely hurt performance. // // Alternative approach is to create `std::exception_ptr` on our // own and place it in the future via `make_exception_future()`. // When it comes to handling, the pointer can be interrogated for // pointee's type with `__cxa_exception_type()` instead of costly // re-throwing (via `std::rethrow_exception()`) and matching with // `catch`. The limitation here is lack of support for hierarchies // of exceptions. The code below checks for exact match only while // `catch` would allow to match against a base class as well. // However, this shouldn't be a big issue for `errorator` as Error // Visitors are already checked for exhaustiveness at compile-time. // // NOTE: `__cxa_exception_type()` is an extension of the language. // It should be available both in GCC and Clang but a fallback // (based on `std::rethrow_exception()` and `catch`) can be made // to handle other platforms if necessary. if (type_info == ErrorT::error_t::get_exception_ptr_type_info()) { // set `state::invalid` in internals of `seastar::future` to not // call `report_failed_future()` during `operator=()`. [[maybe_unused]] auto&& ep = std::move(result).get_exception(); using return_t = std::invoke_result_t; if constexpr (std::is_assignable_v) { result = std::invoke(std::forward(errfunc), ErrorT::error_t::from_exception_ptr(std::move(ep))); } else if constexpr (std::is_same_v) { // void denotes explicit discarding // execute for the sake a side effects. Typically this boils down // to throwing an exception by the handler. std::invoke(std::forward(errfunc), ErrorT::error_t::from_exception_ptr(std::move(ep))); } else { static_assert(_impl::always_false::value, "return of Error Visitor is not assignable to future"); // do nothing with `ep`. } } } auto get_result() && { return std::move(result); } }; template static constexpr auto composer(FuncHead&& head, FuncTail&&... tail) { return [ head = std::forward(head), // perfect forwarding in lambda's closure isn't available in C++17 // using tuple as workaround; see: https://stackoverflow.com/a/49902823 tail = std::make_tuple(std::forward(tail)...) ] (auto&&... args) mutable -> decltype(auto) { if constexpr (std::is_invocable_v) { return std::invoke(std::forward(head), std::forward(args)...); } else if constexpr (sizeof...(FuncTail) > 0) { using next_composer_t = decltype(composer); auto&& next = std::apply(composer, std::move(tail)); return std::invoke(std::move(next), std::forward(args)...); } else { static_assert( std::is_invocable_v || (sizeof...(FuncTail) > 0), "composition is not exhaustive"); } }; } template struct errorated_future_marker{}; template struct errorator { template static inline constexpr bool is_error_v = std::is_base_of_v, T>; static_assert((... && is_error_v), "errorator expects presence of ::is_error in all error types"); template struct contains_once { static constexpr bool value = (0 + ... + std::is_same_v) == 1; }; template struct contains_once> { static constexpr bool value = (... && contains_once::value); }; template static constexpr bool contains_once_v = contains_once::value; static_assert((... && contains_once_v), "no error type in errorator can be duplicated"); struct ready_future_marker{}; struct exception_future_marker{}; private: // see the comment for `using future = _future` below. template class _future {}; template class _future<::crimson::errorated_future_marker> : private seastar::future { using base_t = seastar::future; // 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; // any `seastar::futurize` specialization must be able to access the base. // see : `satisfy_with_result_of()` far below. template friend class seastar::futurize; template friend auto seastar::internal::do_with_impl(T1&& rv1, T2&& rv2, More&&... more); 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...> { private: using step_errorator = errorator; // add ErrorVisitorRetsHeadT only if 1) it's an error type and // 2) isn't already included in the errorator's error set. // It's enough to negate contains_once_v as any errorator<...> // type is already guaranteed to be free of duplications. using next_errorator = std::conditional_t< is_error_v && !step_errorator::template contains_once_v, typename step_errorator::template extend, step_errorator>; public: using type = typename make_errorator::type; }; // finish the recursion template struct make_errorator> { using type = ::crimson::errorator; }; template using make_errorator_t = typename make_errorator::type; using base_t::base_t; template [[gnu::noinline]] static auto _safe_then_handle_errors(Future&& future, ErrorVisitor&& errfunc) { maybe_handle_error_t maybe_handle_error( std::forward(errfunc), std::move(future).get_exception() ); (maybe_handle_error.template handle() , ...); return std::move(maybe_handle_error).get_result(); } public: using errorator_type = ::crimson::errorator; using promise_type = seastar::promise; using base_t::available; using base_t::failed; // need this because of the legacy in PG::do_osd_ops(). using base_t::handle_exception_type; [[gnu::always_inline]] _future(base_t&& base) : base_t(std::move(base)) { } template [[gnu::always_inline]] _future(ready_future_marker, A&&... a) : base_t(::seastar::make_ready_future(std::forward(a)...)) { } [[gnu::always_inline]] _future(exception_future_marker, ::seastar::future_state_base&& state) noexcept : base_t(::seastar::futurize::make_exception_future(std::move(state))) { } [[gnu::always_inline]] _future(exception_future_marker, std::exception_ptr&& ep) noexcept : base_t(::seastar::futurize::make_exception_future(std::move(ep))) { } template