Fix SimpleBarrier
[folly.git] / folly / ExceptionWrapper.h
index 726a27fc810ee367db88946ad035414264f067af..a44d3dd790206523a10f39a88964294dfcae379d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2016 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef FOLLY_EXCEPTIONWRAPPER_H
-#define FOLLY_EXCEPTIONWRAPPER_H
+#pragma once
 
-#include <cassert>
 #include <exception>
 #include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include <folly/ExceptionString.h>
+#include <folly/FBString.h>
 #include <folly/detail/ExceptionWrapper.h>
 
 namespace folly {
@@ -50,11 +54,13 @@ namespace folly {
  * exception_wrapper is designed to handle exception management for both
  * convenience and high performance use cases. make_exception_wrapper is
  * templated on derived type, allowing us to rethrow the exception properly for
- * users that prefer convenience. exception_wrapper is flexible enough to accept
- * any std::exception. For performance sensitive applications, exception_wrapper
- * exposes a get() function. These users can use dynamic_cast to retrieve
- * desired derived types (hence the decision to limit usage to just
- * std::exception instead of void*).
+ * users that prefer convenience. These explicitly named exception types can
+ * therefore be handled without any peformance penalty.  exception_wrapper is
+ * also flexible enough to accept any type. If a caught exception is not of an
+ * explicitly named type, then std::exception_ptr is used to preserve the
+ * exception state. For performance sensitive applications, the accessor methods
+ * can test or extract a pointer to a specific exception type with very little
+ * overhead.
  *
  * Example usage:
  *
@@ -85,57 +91,257 @@ namespace folly {
  *
  * // Thread2: Exceptions are bad!
  * void processResult() {
- *   auto ep = globalExceptionWrapper.get();
- *   if (ep) {
- *     auto faceplant = dynamic_cast<FacePlantException*>(ep);
- *     if (faceplant) {
- *       LOG(ERROR) << "FACEPLANT";
- *     } else {
- *       auto failwhale = dynamic_cast<FailWhaleException*>(ep);
- *       if (failwhale) {
+ *   globalExceptionWrapper.with_exception(
+ *       [&](FacePlantException& faceplant) {
+ *         LOG(ERROR) << "FACEPLANT";
+ *       }) ||
+ *   globalExceptionWrapper.with_exception(
+ *       [&](FailWhaleException& failwhale) {
  *         LOG(ERROR) << "FAILWHALE!";
- *       }
- *     }
- *   }
+ *       }) ||
+ *   LOG(FATAL) << "Unrecognized exception";
  * }
  *
  */
 class exception_wrapper {
+ protected:
+  template <typename Ex>
+  struct optimize;
+
  public:
-  exception_wrapper() : throwfn_(nullptr) { }
+  exception_wrapper() = default;
+
+  // Implicitly construct an exception_wrapper from a qualifying exception.
+  // See the optimize struct for details.
+  template <typename Ex, typename =
+    typename std::enable_if<optimize<typename std::decay<Ex>::type>::value>
+    ::type>
+  /* implicit */ exception_wrapper(Ex&& exn) {
+    typedef typename std::decay<Ex>::type DEx;
+    item_ = std::make_shared<DEx>(std::forward<Ex>(exn));
+    throwfn_ = folly::detail::Thrower<DEx>::doThrow;
+  }
+
+  // The following two constructors are meant to emulate the behavior of
+  // try_and_catch in performance sensitive code as well as to be flexible
+  // enough to wrap exceptions of unknown type. There is an overload that
+  // takes an exception reference so that the wrapper can extract and store
+  // the exception's type and what() when possible.
+  //
+  // The canonical use case is to construct an all-catching exception wrapper
+  // with minimal overhead like so:
+  //
+  //   try {
+  //     // some throwing code
+  //   } catch (const std::exception& e) {
+  //     // won't lose e's type and what()
+  //     exception_wrapper ew{std::current_exception(), e};
+  //   } catch (...) {
+  //     // everything else
+  //     exception_wrapper ew{std::current_exception()};
+  //   }
+  //
+  // try_and_catch is cleaner and preferable. Use it unless you're sure you need
+  // something like this instead.
+  template <typename Ex>
+  explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
+    assign_eptr(eptr, exn);
+  }
+
+  explicit exception_wrapper(std::exception_ptr eptr) {
+    assign_eptr(eptr);
+  }
+
+  // If the exception_wrapper does not contain an exception, std::terminate()
+  // is invoked to assure the [[noreturn]] behaviour.
+  [[noreturn]] void throwException() const;
+
+  explicit operator bool() const {
+    return item_ || eptr_;
+  }
 
-  void throwException() const {
-    if (throwfn_) {
-      throwfn_(item_.get());
+  // This implementation is similar to std::exception_ptr's implementation
+  // where two exception_wrappers are equal when the address in the underlying
+  // reference field both point to the same exception object.  The reference
+  // field remains the same when the exception_wrapper is copied or when
+  // the exception_wrapper is "rethrown".
+  bool operator==(const exception_wrapper& a) const {
+    if (item_) {
+      return a.item_ && item_.get() == a.item_.get();
+    } else {
+      return eptr_ == a.eptr_;
     }
   }
 
-  std::exception* get() { return item_.get(); }
-  const std::exception* get() const { return item_.get(); }
+  bool operator!=(const exception_wrapper& a) const {
+    return !(*this == a);
+  }
+
+  // This will return a non-nullptr only if the exception is held as a
+  // copy.  It is the only interface which will distinguish between an
+  // exception held this way, and by exception_ptr.  You probably
+  // shouldn't use it at all.
+  std::exception* getCopied() { return item_.get(); }
+  const std::exception* getCopied() const { return item_.get(); }
+
+  fbstring what() const;
+  fbstring class_name() const;
+
+  template <class Ex>
+  bool is_compatible_with() const {
+    if (item_) {
+      return dynamic_cast<const Ex*>(item_.get());
+    } else if (eptr_) {
+      try {
+        std::rethrow_exception(eptr_);
+      } catch (typename std::decay<Ex>::type&) {
+        return true;
+      } catch (...) {
+        // fall through
+      }
+    }
+    return false;
+  }
 
-  std::exception* operator->() { return get(); }
-  const std::exception* operator->() const { return get(); }
+  template <class F>
+  bool with_exception(F&& f) {
+    using arg_type = typename functor_traits<F>::arg_type_decayed;
+    return with_exception<arg_type>(std::forward<F>(f));
+  }
+
+  template <class F>
+  bool with_exception(F&& f) const {
+    using arg_type = typename functor_traits<F>::arg_type_decayed;
+    return with_exception<const arg_type>(std::forward<F>(f));
+  }
 
-  std::exception& operator*() { assert(get()); return *get(); }
-  const std::exception& operator*() const { assert(get()); return *get(); }
+  // If this exception wrapper wraps an exception of type Ex, with_exception
+  // will call f with the wrapped exception as an argument and return true, and
+  // will otherwise return false.
+  template <class Ex, class F>
+  typename std::enable_if<
+    std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+    bool>::type
+  with_exception(F f) {
+    return with_exception1<typename std::decay<Ex>::type>(f, this);
+  }
 
-  explicit operator bool() const { return get(); }
+  // Const overload
+  template <class Ex, class F>
+  typename std::enable_if<
+    std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+    bool>::type
+  with_exception(F f) const {
+    return with_exception1<const typename std::decay<Ex>::type>(f, this);
+  }
+
+  // Overload for non-exceptions. Always rethrows.
+  template <class Ex, class F>
+  typename std::enable_if<
+    !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+    bool>::type
+  with_exception(F f) const {
+    try {
+      if (*this) {
+        throwException();
+      }
+    } catch (typename std::decay<Ex>::type& e) {
+      f(e);
+      return true;
+    } catch (...) {
+      // fall through
+    }
+    return false;
+  }
 
   std::exception_ptr getExceptionPtr() const {
+    if (eptr_) {
+      return eptr_;
+    }
+
     try {
-      throwException();
+      if (*this) {
+        throwException();
+      }
     } catch (...) {
       return std::current_exception();
     }
     return std::exception_ptr();
   }
 
- protected:
+protected:
+  template <typename Ex>
+  struct optimize {
+    static const bool value =
+      std::is_base_of<std::exception, Ex>::value &&
+      std::is_copy_assignable<Ex>::value &&
+      !std::is_abstract<Ex>::value;
+  };
+
+  template <typename Ex>
+  void assign_eptr(std::exception_ptr eptr, Ex& e) {
+    this->eptr_ = eptr;
+    this->estr_ = exceptionStr(e).toStdString();
+    this->ename_ = demangle(typeid(e)).toStdString();
+  }
+
+  void assign_eptr(std::exception_ptr eptr) {
+    this->eptr_ = eptr;
+  }
+
+  // Optimized case: if we know what type the exception is, we can
+  // store a copy of the concrete type, and a helper function so we
+  // can rethrow it.
   std::shared_ptr<std::exception> item_;
-  void (*throwfn_)(std::exception*);
+  void (*throwfn_)(std::exception*){nullptr};
+  // Fallback case: store the library wrapper, which is less efficient
+  // but gets the job done.  Also store exceptionPtr() the name of the
+  // exception type, so we can at least get those back out without
+  // having to rethrow.
+  std::exception_ptr eptr_;
+  std::string estr_;
+  std::string ename_;
 
   template <class T, class... Args>
   friend exception_wrapper make_exception_wrapper(Args&&... args);
+
+private:
+  template <typename F>
+  struct functor_traits {
+    template <typename T>
+    struct impl;
+    template <typename C, typename R, typename A>
+    struct impl<R(C::*)(A)> { using arg_type = A; };
+    template <typename C, typename R, typename A>
+    struct impl<R(C::*)(A) const> { using arg_type = A; };
+    using functor_decayed = typename std::decay<F>::type;
+    using functor_op = decltype(&functor_decayed::operator());
+    using arg_type = typename impl<functor_op>::arg_type;
+    using arg_type_decayed = typename std::decay<arg_type>::type;
+  };
+
+  // What makes this useful is that T can be exception_wrapper* or
+  // const exception_wrapper*, and the compiler will use the
+  // instantiation which works with F.
+  template <class Ex, class F, class T>
+  static bool with_exception1(F f, T* that) {
+    if (that->item_) {
+      if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
+        f(*ex);
+        return true;
+      }
+    } else if (that->eptr_) {
+      try {
+        std::rethrow_exception(that->eptr_);
+      } catch (Ex& e) {
+        f(e);
+        return true;
+      } catch (...) {
+        // fall through
+      }
+    }
+    return false;
+  }
 };
 
 template <class T, class... Args>
@@ -146,6 +352,9 @@ exception_wrapper make_exception_wrapper(Args&&... args) {
   return ew;
 }
 
+// For consistency with exceptionStr() functions in String.h
+fbstring exceptionStr(const exception_wrapper& ew);
+
 /*
  * try_and_catch is a simple replacement for try {} catch(){} that allows you to
  * specify which derived exceptions you would like to catch and store in an
@@ -203,13 +412,29 @@ class try_and_catch<LastException, Exceptions...> :
 
   try_and_catch() : Base() {}
 
+  template <typename Ex>
+  typename std::enable_if<!exception_wrapper::optimize<Ex>::value>::type
+  assign_exception(Ex& e, std::exception_ptr eptr) {
+    exception_wrapper::assign_eptr(eptr, e);
+  }
+
+  template <typename Ex>
+  typename std::enable_if<exception_wrapper::optimize<Ex>::value>::type
+  assign_exception(Ex& e, std::exception_ptr /*eptr*/) {
+    this->item_ = std::make_shared<Ex>(e);
+    this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
+  }
+
   template <typename F>
   void call_fn(F&& fn) {
     try {
       Base::call_fn(std::move(fn));
-    } catch (const LastException& e) {
-      this->item_ = std::make_shared<LastException>(e);
-      this->throwfn_ = folly::detail::Thrower<LastException>::doThrow;
+    } catch (LastException& e) {
+      if (typeid(e) == typeid(LastException&)) {
+        assign_exception(e, std::current_exception());
+      } else {
+        exception_wrapper::assign_eptr(std::current_exception(), e);
+      }
     }
   }
 };
@@ -217,7 +442,7 @@ class try_and_catch<LastException, Exceptions...> :
 template<>
 class try_and_catch<> : public exception_wrapper {
  public:
-  try_and_catch() {}
+  try_and_catch() = default;
 
  protected:
   template <typename F>
@@ -225,5 +450,5 @@ class try_and_catch<> : public exception_wrapper {
     fn();
   }
 };
-}
-#endif
+
+} // folly