]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
crimson: bring compile time-verified chaining of errorators.
authorRadoslaw Zarzynski <rzarzyns@redhat.com>
Fri, 6 Sep 2019 11:10:41 +0000 (13:10 +0200)
committerRadoslaw Zarzynski <rzarzyns@redhat.com>
Wed, 20 Nov 2019 19:35:14 +0000 (20:35 +0100)
Signed-off-by: Radoslaw Zarzynski <rzarzyns@redhat.com>
src/crimson/common/errorator.h
src/crimson/os/cyan_store.cc

index fb8214a04d5f286518c6b7432f35abb49f0bcfa0..dc20887f1cc1678d3305302b25d3bf9380365183 100644 (file)
@@ -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 <class T> 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... 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>
@@ -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<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]]
@@ -144,37 +209,150 @@ struct errorator {
 
     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>;
index e8088ff65ef7c8c3d1defb142d2580514f9f7ae3..1ccc78d33fa7bf1ccc86c80e959f7598087d012c 100644 (file)
@@ -211,8 +211,7 @@ CyanStore::get_attr_errorator::future<ceph::bufferptr> CyanStore::get_attr(
     return crimson::make_error<crimson::ct_error::enoent>();
   }
   if (auto found = o->xattr.find(name); found != o->xattr.end()) {
-    return get_attr_errorator::its_error_free(
-      seastar::make_ready_future<ceph::bufferptr>(found->second));
+    return seastar::make_ready_future<ceph::bufferptr>(found->second);
   } else {
     return crimson::make_error<crimson::ct_error::enodata>();
   }