]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
crimson: bring crimson::errorator with its first appliance.
authorRadoslaw Zarzynski <rzarzyns@redhat.com>
Sun, 25 Aug 2019 14:46:02 +0000 (16:46 +0200)
committerRadoslaw Zarzynski <rzarzyns@redhat.com>
Wed, 20 Nov 2019 19:33:55 +0000 (20:33 +0100)
Signed-off-by: Radoslaw Zarzynski <rzarzyns@redhat.com>
src/crimson/common/errorator.h [new file with mode: 0644]
src/crimson/os/cyan_store.cc
src/crimson/os/cyan_store.h
src/crimson/os/futurized_store.h
src/crimson/osd/ops_executer.cc
src/crimson/osd/pg_backend.cc
src/crimson/osd/pg_backend.h

diff --git a/src/crimson/common/errorator.h b/src/crimson/common/errorator.h
new file mode 100644 (file)
index 0000000..fb8214a
--- /dev/null
@@ -0,0 +1,185 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include <exception>
+
+namespace crimson {
+
+namespace _impl {
+  enum class ct_error {
+    enoent,
+    invarg,
+    enodata
+  };
+}
+
+// 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 <_impl::ct_error ErrorV> struct unthrowable_wrapper {
+  unthrowable_wrapper(const unthrowable_wrapper&) = delete;
+  static constexpr unthrowable_wrapper instance{};
+  template <class T> friend const T& make_error();
+
+  // comparison operator for the static_assert in future's ctor.
+  template <_impl::ct_error OtherErrorV>
+  constexpr bool operator==(const unthrowable_wrapper<OtherErrorV>&) const {
+    return OtherErrorV == ErrorV;
+  }
+
+private:
+  // can be used only for initialize the `instance` member
+  explicit unthrowable_wrapper() = default;
+};
+
+template <class T> [[nodiscard]] const T& make_error() {
+  return T::instance;
+}
+
+template <class... WrappedAllowedErrorsT>
+struct errorator {
+  template <class... ValuesT>
+  class future : private seastar::future<ValuesT...> {
+    using base_t = seastar::future<ValuesT...>;
+
+    // TODO: let `exception` use other type than `ct_error`.
+    template <_impl::ct_error V>
+    class exception {
+      exception() = default;
+    public:
+      exception(const exception&) = default;
+    };
+
+    template <class ErrorVisitorT, class FuturatorT>
+    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<ErrorVisitorT>(errfunc)) {
+      }
+
+      template <_impl::ct_error ErrorV>
+      void operator()(const unthrowable_wrapper<ErrorV>& e) {
+        // 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 is necessary.
+        if (type_info == typeid(exception<ErrorV>)) {
+          // set `state::invalid` in internals of `seastar::future` to not
+          // `report_failed_future()` during `operator=()`.
+          std::move(result).get_exception();
+          result = std::forward<ErrorVisitorT>(errfunc)(e);
+        }
+      }
+
+      auto get_result() && {
+        return std::move(result);
+      }
+    };
+
+    using base_t::base_t;
+
+    [[gnu::always_inline]]
+    future(base_t&& base)
+      : base_t(std::move(base)) {
+    }
+
+  public:
+    // initialize future as failed without throwing. `make_exception_future()`
+    // internally uses `std::make_exception_ptr()`. cppreference.com shouldn't
+    // be misinterpreted when it says:
+    //
+    //   "This is done as if executing the following code:
+    //     try {
+    //         throw e;
+    //     } catch(...) {
+    //         return std::current_exception();
+    //     }",
+    //
+    // the "as if" is absolutely crucial because modern GCCs employ optimized
+    // path for it. See:
+    //   * https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=cce8e59224e18858749a2324bce583bcfd160d6c,
+    //   * https://gcc.gnu.org/ml/gcc-patches/2016-08/msg00373.html.
+    //
+    // This behavior, combined with `__cxa_exception_type()` for inspecting
+    // exception's type, allows for throw/catch-free handling of stateless
+    // exceptions (which is fine for error codes). Stateful jumbos would be
+    // actually a bit harder as `_M_get()` is private, and thus rethrowing is
+    // necessary to get to the state inside. However, it's not unthinkable to
+    // see another extension bringing operator*() to the exception pointer...
+    //
+    // TODO: we don't really need to `make_exception_ptr` each time. It still
+    // allocates memory underneath while can be replaced with single instance
+    // per type created on start-up.
+    template <_impl::ct_error ErrorV>
+    future(const unthrowable_wrapper<ErrorV>& e)
+      : base_t(seastar::make_exception_future<ValuesT...>(exception<ErrorV>{})) {
+      // this is `fold expression` of C++17
+      static_assert((... || (e == WrappedAllowedErrorsT::instance)),
+                    "disallowed ct_error");
+    }
+
+    template <class ValueFuncT, class ErrorVisitorT>
+    auto safe_then(ValueFuncT&& valfunc, ErrorVisitorT&& errfunc) {
+      return 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();
+          } else {
+            return futurator_t::apply(std::forward<ValueFuncT>(valfunc),
+                                      std::move(future).get());
+          }
+        });
+    }
+
+    template <class Func>
+    void then(Func&&) = delete;
+
+    friend errorator<WrappedAllowedErrorsT...>;
+  };
+
+  template <class... ValuesT>
+  static future<ValuesT...> its_error_free(seastar::future<ValuesT...>&& plain_future) {
+    return future<ValuesT...>(std::move(plain_future));
+  }
+}; // class errorator
+
+namespace ct_error {
+  using enoent = unthrowable_wrapper<_impl::ct_error::enoent>;
+  using enodata = unthrowable_wrapper<_impl::ct_error::enodata>;
+  using invarg = unthrowable_wrapper<_impl::ct_error::invarg>;
+}
+
+} // namespace crimson
index 60573ee1723836be8e62ebe6b0b966d5264f4bf3..0dafec55cef72487e668990cf9ec4e702c5baca1 100644 (file)
@@ -198,23 +198,25 @@ seastar::future<ceph::bufferlist> CyanStore::read(CollectionRef ch,
   return seastar::make_ready_future<ceph::bufferlist>(o->read(offset, l));
 }
 
-seastar::future<ceph::bufferptr> CyanStore::get_attr(CollectionRef ch,
-                                                     const ghobject_t& oid,
-                                                     std::string_view name) const
+crimson::errorator<crimson::ct_error::enoent,
+                   crimson::ct_error::enodata>::future<ceph::bufferptr>
+CyanStore::get_attr(CollectionRef ch,
+                    const ghobject_t& oid,
+                    std::string_view name) const
 {
   auto c = static_cast<Collection*>(ch.get());
   logger().debug("{} {} {}",
                 __func__, c->get_cid(), oid);
   auto o = c->get_object(oid);
   if (!o) {
-    return seastar::make_exception_future<ceph::bufferptr>(
-      EnoentException(fmt::format("object does not exist: {}", oid)));
+    return crimson::make_error<crimson::ct_error::enoent>();
   }
   if (auto found = o->xattr.find(name); found != o->xattr.end()) {
-    return seastar::make_ready_future<ceph::bufferptr>(found->second);
+    return crimson::errorator<crimson::ct_error::enoent,
+                              crimson::ct_error::enodata>::its_error_free(
+      seastar::make_ready_future<ceph::bufferptr>(found->second));
   } else {
-    return seastar::make_exception_future<ceph::bufferptr>(
-      EnodataException(fmt::format("attr does not exist: {}/{}", oid, name)));
+    return crimson::make_error<crimson::ct_error::enodata>();
   }
 }
 
index 83a816d7a7cac12612b48ce29a1fe3399ddf451a..84311882e52b99bb6920dd442a7091de5bdf1f0b 100644 (file)
@@ -49,9 +49,11 @@ public:
                                   uint64_t offset,
                                   size_t len,
                                   uint32_t op_flags = 0) final;
-  seastar::future<ceph::bufferptr> get_attr(CollectionRef c,
-                                           const ghobject_t& oid,
-                                           std::string_view name) const final;
+  virtual crimson::errorator<crimson::ct_error::enoent,
+                             crimson::ct_error::enodata>::future<ceph::bufferptr>
+  get_attr(CollectionRef c,
+           const ghobject_t& oid,
+           std::string_view name) const final;
   seastar::future<attrs_t> get_attrs(CollectionRef c,
                                      const ghobject_t& oid) final;
 
index 96415653bbfc8fe64b0f2191a1246858fdd44904..afb448b050666e372e66f3d932ebe3ed85815236 100644 (file)
@@ -11,6 +11,7 @@
 
 #include <seastar/core/future.hh>
 
+#include "crimson/osd/exceptions.h"
 #include "include/buffer_fwd.h"
 #include "include/uuid.h"
 #include "osd/osd_types.h"
@@ -76,9 +77,9 @@ public:
                                   uint64_t offset,
                                   size_t len,
                                   uint32_t op_flags = 0) = 0;
-  virtual seastar::future<ceph::bufferptr> get_attr(CollectionRef c,
-                                           const ghobject_t& oid,
-                                           std::string_view name) const = 0;
+  virtual crimson::errorator<crimson::ct_error::enoent,
+                             crimson::ct_error::enodata>::future<ceph::bufferptr>
+  get_attr(CollectionRef c, const ghobject_t& oid, std::string_view name) const = 0;
 
   using attrs_t = std::map<std::string, ceph::bufferptr, std::less<>>;
   virtual seastar::future<attrs_t> get_attrs(CollectionRef c,
index 0e527854e515686646da5ca8e4983c7b90a163a6..385fcb0a8b959193a97b65c94bddb53cad78479d 100644 (file)
@@ -167,19 +167,29 @@ static seastar::future<hobject_t> pgls_filter(
   if (const auto xattr = filter.get_xattr(); !xattr.empty()) {
     logger().debug("pgls_filter: filter is interested in xattr={} for obj={}",
                    xattr, sobj);
-    return backend.getxattr(sobj, xattr).then_wrapped(
-      [&filter, sobj] (auto futval) {
+    return backend.getxattr(sobj, xattr).safe_then(
+      [&filter, sobj] (ceph::bufferptr bp) {
         logger().debug("pgls_filter: got xvalue for obj={}", sobj);
 
         ceph::bufferlist val;
-        if (!futval.failed()) {
-          val.push_back(std::move(futval).get0());
-        } else if (filter.reject_empty_xattr()) {
+        val.push_back(std::move(bp));
+        const bool filtered = filter.filter(sobj, val);
+        return seastar::make_ready_future<hobject_t>(filtered ? sobj : hobject_t{});
+      }, [&filter, sobj] (const auto& e) {
+        // TODO: sugar-coat error handling. Compile-time visitors require
+        // too much of fragile boilerplate.
+        using T = std::decay_t<decltype(e)>;
+        static_assert(std::is_same_v<T, crimson::ct_error::enoent> ||
+                      std::is_same_v<T, crimson::ct_error::enodata>);
+        logger().debug("pgls_filter: got enoent for obj={}", sobj);
+
+        if (filter.reject_empty_xattr()) {
           return seastar::make_ready_future<hobject_t>(hobject_t{});
         }
+        ceph::bufferlist val;
         const bool filtered = filter.filter(sobj, val);
         return seastar::make_ready_future<hobject_t>(filtered ? sobj : hobject_t{});
-    });
+      });
   } else {
     ceph::bufferlist empty_lvalue_bl;
     const bool filtered = filter.filter(sobj, empty_lvalue_bl);
index 4f7b3c88772a1051b553fa7d355d24f3ef59b101..be8a2f8cfdad524f48971514f1bd2d7714f3f2a9 100644 (file)
@@ -115,24 +115,27 @@ PGBackend::_load_os(const hobject_t& oid)
   }
   return store->get_attr(coll,
                          ghobject_t{oid, ghobject_t::NO_GEN, shard},
-                         OI_ATTR).then_wrapped([oid, this](auto fut) {
-    if (fut.failed()) {
-      auto ep = std::move(fut).get_exception();
-      if (!crimson::os::FuturizedStore::EnoentException::is_class_of(ep)) {
-        std::rethrow_exception(ep);
-      }
-      return seastar::make_ready_future<cached_os_t>(
-        os_cache.insert(oid,
-          std::make_unique<ObjectState>(object_info_t{oid}, false)));
-    } else {
+                         OI_ATTR)
+  .safe_then(
+    [oid, this] (ceph::bufferptr bp) {
       // decode existing OI_ATTR's value
       ceph::bufferlist bl;
-      bl.push_back(std::move(fut).get0());
+      bl.push_back(std::move(bp));
       return seastar::make_ready_future<cached_os_t>(
         os_cache.insert(oid,
           std::make_unique<ObjectState>(object_info_t{bl}, true /* exists */)));
-    }
-  });
+    },
+    [oid, this] (const auto& e) {
+      using T = std::decay_t<decltype(e)>;
+      if constexpr (std::is_same_v<T, crimson::ct_error::enoent> ||
+                    std::is_same_v<T, crimson::ct_error::enodata>) {
+        return seastar::make_ready_future<cached_os_t>(
+          os_cache.insert(oid,
+            std::make_unique<ObjectState>(object_info_t{oid}, false)));
+      } else {
+        static_assert(always_false<T>::value, "non-exhaustive visitor!");
+      }
+    });
 }
 
 seastar::future<PGBackend::cached_ss_t>
@@ -143,24 +146,25 @@ PGBackend::_load_ss(const hobject_t& oid)
   }
   return store->get_attr(coll,
                          ghobject_t{oid, ghobject_t::NO_GEN, shard},
-                         SS_ATTR).then_wrapped([oid, this](auto fut) {
-    std::unique_ptr<SnapSet> snapset;
-    if (fut.failed()) {
-      auto ep = std::move(fut).get_exception();
-      if (!crimson::os::FuturizedStore::EnoentException::is_class_of(ep)) {
-        std::rethrow_exception(ep);
-      } else {
-        snapset = std::make_unique<SnapSet>();
-      }
-    } else {
+                         SS_ATTR)
+  .safe_then(
+    [oid, this] (ceph::bufferptr bp) {
       // decode existing SS_ATTR's value
       ceph::bufferlist bl;
-      bl.push_back(std::move(fut).get0());
-      snapset = std::make_unique<SnapSet>(bl);
-    }
-    return seastar::make_ready_future<cached_ss_t>(
-      ss_cache.insert(oid, std::move(snapset)));
-  });
+      bl.push_back(std::move(bp));
+      return seastar::make_ready_future<cached_ss_t>(
+        ss_cache.insert(oid, std::make_unique<SnapSet>(bl)));
+    },
+    [oid, this] (const auto& e) {
+      using T = std::decay_t<decltype(e)>;
+      if constexpr (std::is_same_v<T, crimson::ct_error::enoent> ||
+                    std::is_same_v<T, crimson::ct_error::enodata>) {
+        return seastar::make_ready_future<cached_ss_t>(
+          ss_cache.insert(oid, std::make_unique<SnapSet>()));
+      } else {
+        static_assert(always_false<T>::value, "non-exhaustive visitor!");
+      }
+    });
 }
 
 seastar::future<crimson::osd::acked_peers_t>
@@ -475,22 +479,28 @@ seastar::future<> PGBackend::getxattr(
     name = "_" + aname;
   }
   logger().debug("getxattr on obj={} for attr={}", os.oi.soid, name);
-  return getxattr(os.oi.soid, name).then([&osd_op] (ceph::bufferptr val) {
+  return getxattr(os.oi.soid, name).safe_then([&osd_op] (ceph::bufferptr val) {
     osd_op.outdata.clear();
     osd_op.outdata.push_back(std::move(val));
     osd_op.op.xattr.value_len = osd_op.outdata.length();
+    return seastar::now();
     //ctx->delta_stats.num_rd_kb += shift_round_up(osd_op.outdata.length(), 10);
-  }).handle_exception_type(
-    [] (crimson::os::FuturizedStore::EnoentException&) {
+  }, [] (const auto& e) {
+    using T = std::decay_t<decltype(e)>;
+    if constexpr (std::is_same_v<T, crimson::ct_error::enoent>) {
       return seastar::make_exception_future<>(crimson::osd::object_not_found{});
-  }).handle_exception_type(
-    [] (crimson::os::FuturizedStore::EnodataException&) {
+    } else if constexpr (std::is_same_v<T, crimson::ct_error::enodata>) {
       return seastar::make_exception_future<>(crimson::osd::no_message_available{});
+    } else {
+      static_assert(always_false<T>::value, "non-exhaustive visitor!");
+    }
   });
   //ctx->delta_stats.num_rd++;
 }
 
-seastar::future<ceph::bufferptr> PGBackend::getxattr(
+crimson::errorator<crimson::ct_error::enoent,
+                   crimson::ct_error::enodata>::future<ceph::bufferptr>
+PGBackend::getxattr(
   const hobject_t& soid,
   std::string_view key) const
 {
index b4a7b74a5df44d40853dfb1e72c94beebf2b8ad0..e831fcfe9c7f5de39d0d40323837fa342b341ad4 100644 (file)
@@ -87,7 +87,8 @@ public:
   seastar::future<> getxattr(
     const ObjectState& os,
     OSDOp& osd_op) const;
-  seastar::future<ceph::bufferptr> getxattr(
+  crimson::errorator<crimson::ct_error::enoent,
+                     crimson::ct_error::enodata>::future<ceph::bufferptr> getxattr(
     const hobject_t& soid,
     std::string_view key) const;