// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- // vim: ts=8 sw=2 smarttab expandtab #pragma once #include #include #include #include "crimson/common/utility.h" #include "include/ceph_assert.h" namespace crimson::interruptible { template class parallel_for_each_state; template class interruptible_future_detail; } namespace crimson { // crimson::do_for_each_state is the mirror of seastar::do_for_each_state with FutureT template class do_for_each_state final : public seastar::continuation_base<> { Iterator _begin; Iterator _end; AsyncAction _action; seastar::promise<> _pr; public: do_for_each_state(Iterator begin, Iterator end, AsyncAction action, FutureT&& first_unavailable) : _begin(std::move(begin)), _end(std::move(end)), _action(std::move(action)) { seastar::internal::set_callback(std::move(first_unavailable), this); } virtual void run_and_dispose() noexcept override { std::unique_ptr zis(this); if (_state.failed()) { _pr.set_urgent_state(std::move(_state)); return; } while (_begin != _end) { auto f = seastar::futurize_invoke(_action, *_begin); ++_begin; if (f.failed()) { f._forward_to(std::move(_pr)); return; } if (!f.available() || seastar::need_preempt()) { _state = {}; seastar::internal::set_callback(std::move(f), this); zis.release(); return; } } _pr.set_value(); } task* waiting_task() noexcept override { return _pr.waiting_task(); } FutureT get_future() { return _pr.get_future(); } }; template> inline FutureT do_for_each_impl(Iterator begin, Iterator end, AsyncAction action) { while (begin != end) { auto f = seastar::futurize_invoke(action, *begin); ++begin; if (f.failed()) { return f; } if (!f.available() || seastar::need_preempt()) { // s will be freed by run_and_dispose() auto* s = new crimson::do_for_each_state{ std::move(begin), std::move(end), std::move(action), std::move(f)}; return s->get_future(); } } return seastar::make_ready_future<>(); } template inline auto do_for_each(Iterator begin, Iterator end, AsyncAction action) { return ::crimson::do_for_each_impl(begin, end, std::move(action)); } 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 repeat(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 stop) mutable { if (stop == seastar::stop_iteration::yes) { return errorator_t::template make_ready_future<>(); } return ::crimson::repeat( 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(); } 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; protected: std::exception_ptr to_exception_ptr() const { const auto* concrete_error = static_cast(this); return concrete_error->to_exception_ptr(); } 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; } static auto exception_ptr() { return make().to_exception_ptr(); } template static auto handle(Func&& func) { return [ func = std::forward(func) ] (const unthrowable_wrapper& raw_error) mutable -> decltype(auto) { if constexpr (std::is_invocable_v) { // check whether the handler wants to take the raw error object which // would be the case if it wants conditionally handle-or-pass-further. return std::invoke(std::forward(func), ErrorV, std::move(raw_error)); } else 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) { return [ func = std::forward(func) ] (stateful_error_t&& e) mutable -> decltype(auto) { if constexpr (std::is_invocable_v) { return std::invoke(std::forward(func)); } try { std::rethrow_exception(e.ep); } catch (const ErrorT& obj) { if constexpr (std::is_invocable_v) { return std::invoke(std::forward(func), obj, e); } else if constexpr (std::is_invocable_v) { 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 if constexpr (seastar::Future) { // result is seastar::future but return_t is e.g. int. If so, // the else clause cannot be used as seastar::future lacks // errorator_type member. result = seastar::make_ready_future( std::invoke(std::forward(errfunc), ErrorT::error_t::from_exception_ptr(std::move(ep)))); } else { result = FuturatorT::type::errorator_type::template make_ready_future( std::invoke(std::forward(errfunc), ErrorT::error_t::from_exception_ptr(std::move(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 class parallel_for_each_state; template static inline constexpr bool is_error_v = std::is_base_of_v, T>; template struct errorator; template static inline typename errorator::template future<> parallel_for_each(Iterator first, Iterator last, Func&& func) noexcept; template struct errorator { 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 [[nodiscard]] _future {}; template class [[nodiscard]] _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 struct 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>; using maybe_head_ertr = get_errorator_t; using next_errorator = typename _next_errorator::template extend_ertr; 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(); } protected: using base_t::get_exception; 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)) { } base_t to_base() && { return std::move(*this); } 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