exception_ptr -> exception_wrapper migration
authorJames Sedgwick <jsedgwick@fb.com>
Thu, 8 Jan 2015 15:48:23 +0000 (07:48 -0800)
committerViswanath Sivakumar <viswanath@fb.com>
Tue, 13 Jan 2015 19:01:05 +0000 (11:01 -0800)
Summary:
integrate exception_wrapper everywhere, and deprecate public methods that take ptrs directly to discourage their use
note that this will break for throwing non-exceptions, which is probably fine

this change opens the door to interesting optimizations for those interested, e.g. Future::then<Exn1, Exn2>(/* func throwing Exn1 and Exn2 */) that autowraps the given types into the resultant future

new benchmark:
```
throwAndCatch                                               23.69us   42.21K
throwAndCatchWrapped                             119.53%    19.82us   50.45K
throwWrappedAndCatchWrapped                      350.16%     6.77us  147.80K
```

Test Plan: existing unit tests, suspected potential perf wins confirmed by benchmark, will wait for windtunnel to see other wins/regressions

Reviewed By: hans@fb.com

Subscribers: search-fbcode-diffs@, apodsiadlo, alikhtarov, andrii, trunkagent, fugalh, njormrod, folly-diffs@, bmatheny

FB internal diff: D1644912

Signature: t1:1644912:1420731849:3dc658dc03bfd6e75d61158808c7dad96092ecfb

12 files changed:
folly/ExceptionWrapper.h
folly/wangle/futures/Future-inl.h
folly/wangle/futures/Future.h
folly/wangle/futures/Promise-inl.h
folly/wangle/futures/Promise.h
folly/wangle/futures/Try-inl.h
folly/wangle/futures/Try.h
folly/wangle/futures/detail/Core.h
folly/wangle/futures/test/Benchmark.cpp
folly/wangle/futures/test/FutureTest.cpp
folly/wangle/futures/test/Interrupts.cpp
folly/wangle/futures/test/Try.cpp

index 2fca5c68d26a4129458b0b77c8e2a95adad70046..14df3299c56a4950b789da079d58e72ebd843564 100644 (file)
@@ -102,15 +102,50 @@ namespace folly {
  *
  */
 class exception_wrapper {
+ protected:
+  template <typename Ex>
+  struct optimize;
+
  public:
-  exception_wrapper() : throwfn_(nullptr) { }
-
-  // Implicitly construct an exception_wrapper from any std::exception
-  template <typename T, typename =
-    typename std::enable_if<std::is_base_of<std::exception, T>::value>::type>
-  /* implicit */ exception_wrapper(T&& exn) {
-    item_ = std::make_shared<T>(std::forward<T>(exn));
-    throwfn_ = folly::detail::Thrower<T>::doThrow;
+  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<Ex>::value>::type>
+  /* implicit */ exception_wrapper(Ex&& exn) {
+    item_ = std::make_shared<Ex>(std::forward<Ex>(exn));
+    throwfn_ = folly::detail::Thrower<Ex>::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);
   }
 
   void throwException() const {
@@ -185,14 +220,41 @@ class exception_wrapper {
     return false;
   }
 
+  // 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);
+  }
+
+  // Const overload
   template <class Ex, class F>
-  bool with_exception(F f) {
-    return with_exception1<Ex>(f, this);
+  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>
-  bool with_exception(F f) const {
-    return with_exception1<const Ex>(f, this);
+  typename std::enable_if<
+    !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+    bool>::type
+  with_exception(F f) const {
+    try {
+      throwException();
+    } catch (typename std::decay<Ex>::type& e) {
+      f(e);
+      return true;
+    } catch (...) {
+      // fall through
+    }
+    return false;
   }
 
   std::exception_ptr getExceptionPtr() const {
@@ -209,11 +271,30 @@ class exception_wrapper {
   }
 
 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
@@ -318,29 +399,14 @@ class try_and_catch<LastException, Exceptions...> :
   try_and_catch() : Base() {}
 
   template <typename Ex>
-  void assign_eptr(Ex& e) {
-    this->eptr_ = std::current_exception();
-    this->estr_ = exceptionStr(e).toStdString();
-    this->ename_ = demangle(typeid(e)).toStdString();
-  }
-
-  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>
-  typename std::enable_if<!optimize<Ex>::value>::type
-  assign_exception(Ex& e) {
-    assign_eptr(e);
+  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<optimize<Ex>::value>::type
-  assign_exception(Ex& e) {
+  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;
   }
@@ -351,9 +417,9 @@ class try_and_catch<LastException, Exceptions...> :
       Base::call_fn(std::move(fn));
     } catch (LastException& e) {
       if (typeid(e) == typeid(LastException&)) {
-        assign_exception(e);
+        assign_exception(e, std::current_exception());
       } else {
-        assign_eptr(e);
+        exception_wrapper::assign_eptr(std::current_exception(), e);
       }
     }
   }
index 2c2f0aa68e6843129afded6fcfca2d5599028986..a980ff0302b36e410ed0a69f2d3560947a157a65 100644 (file)
@@ -131,8 +131,8 @@ Future<T>::then(F&& func) {
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
       p->fulfil([&]() {
-          return (*funcm)(std::move(t));
-        });
+        return (*funcm)(std::move(t));
+      });
     });
 
   return std::move(f);
@@ -159,7 +159,7 @@ Future<T>::then(F&& func) {
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
       if (t.hasException()) {
-        p->setException(t.getException());
+        p->setException(std::move(t.exception()));
       } else {
         p->fulfil([&]() {
           return (*funcm)(std::move(t.value()));
@@ -189,7 +189,7 @@ Future<T>::then(F&& func) {
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
       if (t.hasException()) {
-        p->setException(t.getException());
+        p->setException(std::move(t.exception()));
       } else {
         p->fulfil([&]() {
           return (*funcm)();
@@ -224,10 +224,12 @@ Future<T>::then(F&& func) {
         auto f2 = (*funcm)(std::move(t));
         // that didn't throw, now we can steal p
         f2.setCallback_([p](Try<B>&& b) mutable {
-            p->fulfilTry(std::move(b));
-          });
+          p->fulfilTry(std::move(b));
+        });
+      } catch (const std::exception& e) {
+        p->setException(exception_wrapper(std::current_exception(), e));
       } catch (...) {
-        p->setException(std::current_exception());
+        p->setException(exception_wrapper(std::current_exception()));
       }
     });
 
@@ -255,15 +257,17 @@ Future<T>::then(F&& func) {
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
       if (t.hasException()) {
-        p->setException(t.getException());
+        p->setException(std::move(t.exception()));
       } else {
         try {
           auto f2 = (*funcm)(std::move(t.value()));
           f2.setCallback_([p](Try<B>&& b) mutable {
-              p->fulfilTry(std::move(b));
-            });
+            p->fulfilTry(std::move(b));
+          });
+        } catch (const std::exception& e) {
+          p->setException(exception_wrapper(std::current_exception(), e));
         } catch (...) {
-          p->setException(std::current_exception());
+          p->setException(exception_wrapper(std::current_exception()));
         }
       }
     });
@@ -291,15 +295,17 @@ Future<T>::then(F&& func) {
   setCallback_(
     [p, funcm](Try<T>&& t) mutable {
       if (t.hasException()) {
-        p->setException(t.getException());
+        p->setException(t.exception());
       } else {
         try {
           auto f2 = (*funcm)();
           f2.setCallback_([p](Try<B>&& b) mutable {
-              p->fulfilTry(std::move(b));
-            });
+            p->fulfilTry(std::move(b));
+          });
+        } catch (const std::exception& e) {
+          p->setException(exception_wrapper(std::current_exception(), e));
         } catch (...) {
-          p->setException(std::current_exception());
+          p->setException(exception_wrapper(std::current_exception()));
         }
       }
     });
@@ -329,17 +335,13 @@ Future<T>::onError(F&& func) {
   auto pm = folly::makeMoveWrapper(std::move(p));
   auto funcm = folly::makeMoveWrapper(std::move(func));
   setCallback_([pm, funcm](Try<T>&& t) mutable {
-    try {
-      t.throwIfFailed();
-    } catch (Exn& e) {
-      pm->fulfil([&]{
-        return (*funcm)(e);
-      });
-      return;
-    } catch (...) {
-      // fall through
+    if (!t.template withException<Exn>([&] (Exn& e) {
+          pm->fulfil([&]{
+            return (*funcm)(e);
+          });
+        })) {
+      pm->fulfilTry(std::move(t));
     }
-    pm->fulfilTry(std::move(t));
   });
 
   return f;
@@ -362,22 +364,20 @@ Future<T>::onError(F&& func) {
   auto pm = folly::makeMoveWrapper(std::move(p));
   auto funcm = folly::makeMoveWrapper(std::move(func));
   setCallback_([pm, funcm](Try<T>&& t) mutable {
-    try {
-      t.throwIfFailed();
-    } catch (Exn& e) {
-      try {
-        auto f2 = (*funcm)(e);
-        f2.setCallback_([pm](Try<T>&& t2) mutable {
-          pm->fulfilTry(std::move(t2));
-        });
-      } catch (...) {
-        pm->setException(std::current_exception());
-      }
-      return;
-    } catch (...) {
-      // fall through
+    if (!t.template withException<Exn>([&] (Exn& e) {
+          try {
+            auto f2 = (*funcm)(e);
+            f2.setCallback_([pm](Try<T>&& t2) mutable {
+              pm->fulfilTry(std::move(t2));
+            });
+          } catch (const std::exception& e) {
+            pm->setException(exception_wrapper(std::current_exception(), e));
+          } catch (...) {
+            pm->setException(exception_wrapper(std::current_exception()));
+          }
+        })) {
+      pm->fulfilTry(std::move(t));
     }
-    pm->fulfilTry(std::move(t));
   });
 
   return f;
@@ -433,8 +433,8 @@ bool Future<T>::isReady() const {
 }
 
 template <class T>
-void Future<T>::raise(std::exception_ptr exception) {
-  core_->raise(exception);
+void Future<T>::raise(exception_wrapper exception) {
+  core_->raise(std::move(exception));
 }
 
 // makeFuture
@@ -483,32 +483,38 @@ Future<T> makeFuture(std::exception_ptr const& e) {
   return std::move(f);
 }
 
+template <class T>
+Future<T> makeFuture(exception_wrapper ew) {
+  Promise<T> p;
+  p.setException(std::move(ew));
+  return p.getFuture();
+}
+
 template <class T, class E>
 typename std::enable_if<std::is_base_of<std::exception, E>::value,
                         Future<T>>::type
 makeFuture(E const& e) {
   Promise<T> p;
   auto f = p.getFuture();
-  p.fulfil([&]() -> T { throw e; });
+  p.setException(make_exception_wrapper<E>(e));
   return std::move(f);
 }
 
 template <class T>
 Future<T> makeFuture(Try<T>&& t) {
-  try {
+  if (t.hasException()) {
+    return makeFuture<T>(std::move(t.exception()));
+  } else {
     return makeFuture<T>(std::move(t.value()));
-  } catch (...) {
-    return makeFuture<T>(std::current_exception());
   }
 }
 
 template <>
 inline Future<void> makeFuture(Try<void>&& t) {
-  try {
-    t.throwIfFailed();
+  if (t.hasException()) {
+    return makeFuture<void>(std::move(t.exception()));
+  } else {
     return makeFuture();
-  } catch (...) {
-    return makeFuture<void>(std::current_exception());
   }
 }
 
@@ -730,7 +736,9 @@ namespace {
             t.value();
             p.setException(TimedOut());
           } catch (std::exception const& e) {
-            p.setException(std::current_exception());
+            p.setException(exception_wrapper(std::current_exception(), e));
+          } catch (...) {
+            p.setException(exception_wrapper(std::current_exception()));
           }
           baton.post();
         }
@@ -803,8 +811,12 @@ Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) {
         try {
           t.throwIfFailed();
           ctx->promise.setException(std::move(ctx->exception));
-        } catch (std::exception const&) {
-          ctx->promise.setException(std::current_exception());
+        } catch (std::exception const& e) {
+          ctx->promise.setException(
+              exception_wrapper(std::current_exception(), e));
+        } catch (...) {
+          ctx->promise.setException(
+              exception_wrapper(std::current_exception()));
         }
       }
     });
index 066b7465b316ecb4da40438e96efd3f63695c614..f94a6b7dfca8a1ddb2984e5292996649ac10de25 100644 (file)
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include <folly/MoveWrapper.h>
+#include <folly/wangle/futures/Deprecated.h>
 #include <folly/wangle/futures/Promise.h>
 #include <folly/wangle/futures/Try.h>
 #include <folly/wangle/futures/WangleException.h>
@@ -445,7 +446,8 @@ class Future {
 
   template <class E>
   void raise(E&& exception) {
-    raise(std::make_exception_ptr(std::forward<E>(exception)));
+    raise(make_exception_wrapper<typename std::remove_reference<E>::type>(
+        std::move(exception)));
   }
 
   /// Raise an interrupt. If the promise holder has an interrupt
@@ -456,7 +458,7 @@ class Future {
   /// preventing the asynchronous operation (if in time), and the promise
   /// holder setting an exception on the future. (That may happen
   /// asynchronously, of course.)
-  void raise(std::exception_ptr interrupt);
+  void raise(exception_wrapper interrupt);
 
   void cancel() {
     raise(FutureCancellation());
@@ -527,7 +529,11 @@ auto makeFutureTry(
 ///
 ///   auto f = makeFuture<string>(std::current_exception());
 template <class T>
-Future<T> makeFuture(std::exception_ptr const& e);
+Future<T> makeFuture(std::exception_ptr const& e) DEPRECATED;
+
+/// Make a failed Future from an exception_wrapper.
+template <class T>
+Future<T> makeFuture(exception_wrapper ew);
 
 /** Make a Future from an exception type E that can be passed to
   std::make_exception_ptr(). */
index ec1afe1768a28a408e78f2dcc6c4e295054ffa19..d7ad250c52f987c22e3da5c09565f47494b86994 100644 (file)
@@ -78,19 +78,31 @@ Future<T> Promise<T>::getFuture() {
 
 template <class T>
 template <class E>
-void Promise<T>::setException(E const& e) {
-  setException(std::make_exception_ptr<E>(e));
+typename std::enable_if<std::is_base_of<std::exception, E>::value>::type
+Promise<T>::setException(E const& e) {
+  setException(make_exception_wrapper<E>(e));
 }
 
 template <class T>
 void Promise<T>::setException(std::exception_ptr const& e) {
+  try {
+    std::rethrow_exception(e);
+  } catch (const std::exception& e) {
+    setException(exception_wrapper(std::current_exception(), e));
+  } catch (...) {
+    setException(exception_wrapper(std::current_exception()));
+  }
+}
+
+template <class T>
+void Promise<T>::setException(exception_wrapper ew) {
   throwIfFulfilled();
-  core_->setResult(Try<T>(e));
+  core_->setResult(Try<T>(std::move(ew)));
 }
 
 template <class T>
 void Promise<T>::setInterruptHandler(
-  std::function<void(std::exception_ptr const&)> fn) {
+  std::function<void(exception_wrapper const&)> fn) {
   core_->setInterruptHandler(std::move(fn));
 }
 
index 7d8609bcc81deea2aeffd57323c171c3808daa08..af324a9dad80bc18dae4e071b1e56a800b46c8f9 100644 (file)
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <folly/wangle/futures/Deprecated.h>
 #include <folly/wangle/futures/Try.h>
 #include <functional>
 
@@ -42,6 +43,9 @@ public:
     once, thereafter Future already retrieved exception will be raised. */
   Future<T> getFuture();
 
+  /** Fulfil the Promise with an exception_wrapper */
+  void setException(exception_wrapper ew);
+
   /** Fulfil the Promise with an exception_ptr, e.g.
     try {
       ...
@@ -49,20 +53,22 @@ public:
       p.setException(std::current_exception());
     }
     */
-  void setException(std::exception_ptr const&);
+  void setException(std::exception_ptr const&) DEPRECATED;
 
   /** Fulfil the Promise with an exception type E, which can be passed to
     std::make_exception_ptr(). Useful for originating exceptions. If you
-    caught an exception the exception_ptr form is more appropriate.
+    caught an exception the exception_wrapper form is more appropriate.
     */
-  template <class E> void setException(E const&);
+  template <class E>
+  typename std::enable_if<std::is_base_of<std::exception, E>::value>::type
+  setException(E const&);
 
   /// Set an interrupt handler to handle interrupts. See the documentation for
   /// Future::raise(). Your handler can do whatever it wants, but if you
   /// bother to set one then you probably will want to fulfil the promise with
   /// an exception (or special value) indicating how the interrupt was
   /// handled.
-  void setInterruptHandler(std::function<void(std::exception_ptr const&)>);
+  void setInterruptHandler(std::function<void(exception_wrapper const&)>);
 
   /** Fulfil this Promise (only for Promise<void>) */
   void setValue();
index 44f2cea46936f08fbd0049744ac4eb774374f9f8..63a005bd7672913b8db2470e61b28c573aa3d469 100644 (file)
@@ -27,7 +27,7 @@ Try<T>::Try(Try<T>&& t) : contains_(t.contains_) {
   if (contains_ == Contains::VALUE) {
     new (&value_)T(std::move(t.value_));
   } else if (contains_ == Contains::EXCEPTION) {
-    new (&e_)std::exception_ptr(t.e_);
+    new (&e_)std::unique_ptr<exception_wrapper>(std::move(t.e_));
   }
 }
 
@@ -38,7 +38,7 @@ Try<T>& Try<T>::operator=(Try<T>&& t) {
   if (contains_ == Contains::VALUE) {
     new (&value_)T(std::move(t.value_));
   } else if (contains_ == Contains::EXCEPTION) {
-    new (&e_)std::exception_ptr(t.e_);
+    new (&e_)std::unique_ptr<exception_wrapper>(std::move(t.e_));
   }
   return *this;
 }
@@ -48,7 +48,7 @@ Try<T>::~Try() {
   if (contains_ == Contains::VALUE) {
     value_.~T();
   } else if (contains_ == Contains::EXCEPTION) {
-    e_.~exception_ptr();
+    e_.~unique_ptr<exception_wrapper>();
   }
 }
 
@@ -68,7 +68,7 @@ template <class T>
 void Try<T>::throwIfFailed() const {
   if (contains_ != Contains::VALUE) {
     if (contains_ == Contains::EXCEPTION) {
-      std::rethrow_exception(e_);
+      e_->throwException();
     } else {
       throw UsingUninitializedTry();
     }
@@ -77,7 +77,7 @@ void Try<T>::throwIfFailed() const {
 
 void Try<void>::throwIfFailed() const {
   if (!hasValue_) {
-    std::rethrow_exception(e_);
+    e_->throwException();
   }
 }
 
@@ -97,10 +97,11 @@ typename std::enable_if<
 makeTryFunction(F&& f) {
   typedef typename std::result_of<F()>::type ResultType;
   try {
-    auto value = f();
-    return Try<ResultType>(std::move(value));
+    return Try<ResultType>(f());
+  } catch (std::exception& e) {
+    return Try<ResultType>(exception_wrapper(std::current_exception(), e));
   } catch (...) {
-    return Try<ResultType>(std::current_exception());
+    return Try<ResultType>(exception_wrapper(std::current_exception()));
   }
 }
 
@@ -112,8 +113,10 @@ makeTryFunction(F&& f) {
   try {
     f();
     return Try<void>();
+  } catch (std::exception& e) {
+    return Try<void>(exception_wrapper(std::current_exception(), e));
   } catch (...) {
-    return Try<void>(std::current_exception());
+    return Try<void>(exception_wrapper(std::current_exception()));
   }
 }
 
index a90aa4d52872544c3f83377c0f80da7a7c8422be..a1101d379c79ab1c7912c31fac7d5acd7ebe156c 100644 (file)
 #include <type_traits>
 #include <exception>
 #include <algorithm>
+#include <folly/ExceptionWrapper.h>
 #include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/wangle/futures/Deprecated.h>
 #include <folly/wangle/futures/WangleException.h>
 
 namespace folly { namespace wangle {
@@ -41,7 +44,19 @@ class Try {
   Try() : contains_(Contains::NOTHING) {}
   explicit Try(const T& v) : contains_(Contains::VALUE), value_(v) {}
   explicit Try(T&& v) : contains_(Contains::VALUE), value_(std::move(v)) {}
-  explicit Try(std::exception_ptr e) : contains_(Contains::EXCEPTION), e_(e) {}
+  explicit Try(exception_wrapper e)
+    : contains_(Contains::EXCEPTION),
+      e_(folly::make_unique<exception_wrapper>(std::move(e))) {}
+  explicit Try(std::exception_ptr ep) DEPRECATED
+    : contains_(Contains::EXCEPTION) {
+    try {
+      std::rethrow_exception(ep);
+    } catch (const std::exception& e) {
+      e_ = folly::make_unique<exception_wrapper>(std::current_exception(), e);
+    } catch (...) {
+      e_ = folly::make_unique<exception_wrapper>(std::current_exception());
+    }
+  }
 
   // move
   Try(Try<T>&& t);
@@ -67,19 +82,31 @@ class Try {
   bool hasValue() const { return contains_ == Contains::VALUE; }
   bool hasException() const { return contains_ == Contains::EXCEPTION; }
 
-  std::exception_ptr getException() const {
+  template <class Ex>
+  bool hasException() const {
+    return hasException() && e_->is_compatible_with<Ex>();
+  }
+
+  exception_wrapper& exception() {
     if (UNLIKELY(!hasException())) {
-      throw WangleException(
-          "getException(): Try does not contain an exception");
+      throw WangleException("exception(): Try does not contain an exception");
     }
-    return e_;
+    return *e_;
+  }
+
+  template <class Ex, class F>
+  bool withException(F func) const {
+    if (!hasException()) {
+      return false;
+    }
+    return e_->with_exception<Ex>(std::move(func));
   }
 
  private:
   Contains contains_;
   union {
     T value_;
-    std::exception_ptr e_;
+    std::unique_ptr<exception_wrapper> e_;
   };
 };
 
@@ -87,7 +114,29 @@ template <>
 class Try<void> {
  public:
   Try() : hasValue_(true) {}
-  explicit Try(std::exception_ptr e) : hasValue_(false), e_(e) {}
+  explicit Try(exception_wrapper e)
+    : hasValue_(false),
+      e_(folly::make_unique<exception_wrapper>(std::move(e))) {}
+  explicit Try(std::exception_ptr ep) DEPRECATED : hasValue_(false) {
+    try {
+      std::rethrow_exception(ep);
+    } catch (const std::exception& e) {
+      e_ = folly::make_unique<exception_wrapper>(std::current_exception(), e);
+    } catch (...) {
+      e_ = folly::make_unique<exception_wrapper>(std::current_exception());
+    }
+  }
+
+  Try& operator=(const Try<void>& t) {
+    hasValue_ = t.hasValue_;
+    if (t.e_) {
+      e_ = folly::make_unique<exception_wrapper>(*t.e_);
+    }
+    return *this;
+  }
+  Try(const Try<void>& t) {
+    *this = t;
+  }
 
   void value() const { throwIfFailed(); }
   void operator*() const { return value(); }
@@ -97,17 +146,29 @@ class Try<void> {
   bool hasValue() const { return hasValue_; }
   bool hasException() const { return !hasValue_; }
 
-  std::exception_ptr getException() const {
+  template <class Ex>
+  bool hasException() const {
+    return hasException() && e_->is_compatible_with<Ex>();
+  }
+
+  exception_wrapper& exception() {
     if (UNLIKELY(!hasException())) {
-      throw WangleException(
-          "getException(): Try does not contain an exception");
+      throw WangleException("exception(): Try does not contain an exception");
+    }
+    return *e_;
+  }
+
+  template <class Ex, class F>
+  bool withException(F func) const {
+    if (!hasException()) {
+      return false;
     }
-    return e_;
+    return e_->with_exception<Ex>(std::move(func));
   }
 
  private:
   bool hasValue_;
-  std::exception_ptr e_;
+  std::unique_ptr<exception_wrapper> e_{nullptr};
 };
 
 /**
index cd9e5af5e09502f856f91470f1ae27e8b6db282e..ae08102723b3270e9c39b599fae098cdabc4b401 100644 (file)
@@ -132,7 +132,7 @@ class Core : protected FSM<State> {
   // Called by a destructing Promise
   void detachPromise() {
     if (!ready()) {
-      setResult(Try<T>(std::make_exception_ptr(BrokenPromise())));
+      setResult(Try<T>(exception_wrapper(BrokenPromise())));
     }
     detachOne();
   }
@@ -154,18 +154,18 @@ class Core : protected FSM<State> {
     executor_ = x;
   }
 
-  void raise(std::exception_ptr const& e) {
+  void raise(exception_wrapper const& e) {
     FSM_START
       case State::Interruptible:
         FSM_UPDATE2(State::Interrupted,
-          [&]{ interrupt_ = e; },
-          [&]{ interruptHandler_(interrupt_); });
+          [&]{ interrupt_ = folly::make_unique<exception_wrapper>(e); },
+          [&]{ interruptHandler_(*interrupt_); });
         break;
 
       case State::Waiting:
       case State::Interrupted:
         FSM_UPDATE(State::Interrupted,
-          [&]{ interrupt_ = e; });
+          [&]{ interrupt_ = folly::make_unique<exception_wrapper>(e); });
         break;
 
       case State::Done:
@@ -173,7 +173,7 @@ class Core : protected FSM<State> {
     FSM_END
   }
 
-  void setInterruptHandler(std::function<void(std::exception_ptr const&)> fn) {
+  void setInterruptHandler(std::function<void(exception_wrapper const&)> fn) {
     FSM_START
       case State::Waiting:
       case State::Interruptible:
@@ -182,7 +182,7 @@ class Core : protected FSM<State> {
         break;
 
       case State::Interrupted:
-        fn(interrupt_);
+        fn(*interrupt_);
         FSM_BREAK
 
       case State::Done:
@@ -228,8 +228,8 @@ class Core : protected FSM<State> {
   std::atomic<unsigned char> detached_ {0};
   std::atomic<bool> active_ {true};
   std::atomic<Executor*> executor_ {nullptr};
-  std::exception_ptr interrupt_;
-  std::function<void(std::exception_ptr const&)> interruptHandler_;
+  std::unique_ptr<exception_wrapper> interrupt_;
+  std::function<void(exception_wrapper const&)> interruptHandler_;
 };
 
 template <typename... Ts>
index 863fa95e6924838fc4424e470d8ea85df11a2b78..dc811ce318c180d4eb900811d93b7f05a2c93674 100644 (file)
@@ -165,6 +165,132 @@ BENCHMARK_RELATIVE(contention) {
   producer.join();
 }
 
+BENCHMARK_DRAW_LINE();
+
+// The old way. Throw an exception, and rethrow to access it upstream.
+void throwAndCatchImpl() {
+  makeFuture()
+      .then([](Try<void>&&){ throw std::runtime_error("oh no"); })
+      .then([](Try<void>&& t) {
+        try {
+          t.value();
+        } catch(const std::runtime_error& e) {
+          // ...
+          return;
+        }
+        CHECK(false);
+      });
+}
+
+// Not much better. Throw an exception, and access it via the wrapper upstream.
+// Actually a little worse due to wrapper overhead. then() won't know that the
+// exception is a runtime_error, so will have to store it as an exception_ptr
+// anyways. withException will therefore have to rethrow. Note that if we threw
+// std::exception instead, we would see some wins, as that's the type then()
+// will try to wrap, so no exception_ptrs/rethrows are necessary.
+void throwAndCatchWrappedImpl() {
+  makeFuture()
+      .then([](Try<void>&&){ throw std::runtime_error("oh no"); })
+      .then([](Try<void>&& t) {
+        auto caught = t.withException<std::runtime_error>(
+            [](const std::runtime_error& e){
+              // ...
+            });
+        CHECK(caught);
+      });
+}
+
+// Better. Wrap an exception, and rethrow to access it upstream.
+void throwWrappedAndCatchImpl() {
+  makeFuture()
+      .then([](Try<void>&&){
+        return makeFuture<void>(std::runtime_error("oh no"));
+      })
+      .then([](Try<void>&& t) {
+        try {
+          t.value();
+        } catch(const std::runtime_error& e) {
+          // ...
+          return;
+        }
+        CHECK(false);
+      });
+}
+
+// The new way. Wrap an exception, and access it via the wrapper upstream
+void throwWrappedAndCatchWrappedImpl() {
+  makeFuture()
+      .then([](Try<void>&&){
+        return makeFuture<void>(std::runtime_error("oh no"));
+      })
+      .then([](Try<void>&& t){
+        auto caught = t.withException<std::runtime_error>(
+            [](const std::runtime_error& e){
+              // ...
+            });
+        CHECK(caught);
+      });
+}
+
+// Simulate heavy contention on func
+void contend(void(*func)()) {
+  folly::BenchmarkSuspender s;
+  const int N = 100;
+  const int iters = 1000;
+  pthread_barrier_t barrier;
+  pthread_barrier_init(&barrier, nullptr, N+1);
+  std::vector<std::thread> threads;
+  for (int i = 0; i < N; i++) {
+    threads.push_back(std::thread([&](){
+      pthread_barrier_wait(&barrier);
+      for (int j = 0; j < iters; j++) {
+        func();
+      }
+    }));
+  }
+  pthread_barrier_wait(&barrier);
+  s.dismiss();
+  for (auto& t : threads) {
+    t.join();
+  }
+  s.rehire();
+  pthread_barrier_destroy(&barrier);
+}
+
+BENCHMARK(throwAndCatch) {
+  throwAndCatchImpl();
+}
+
+BENCHMARK_RELATIVE(throwAndCatchWrapped) {
+  throwAndCatchWrappedImpl();
+}
+
+BENCHMARK_RELATIVE(throwWrappedAndCatch) {
+  throwWrappedAndCatchImpl();
+}
+
+BENCHMARK_RELATIVE(throwWrappedAndCatchWrapped) {
+  throwWrappedAndCatchWrappedImpl();
+}
+
+BENCHMARK_DRAW_LINE();
+
+BENCHMARK(throwAndCatchContended) {
+  contend(throwAndCatchImpl);
+}
+
+BENCHMARK_RELATIVE(throwAndCatchWrappedContended) {
+  contend(throwAndCatchWrappedImpl);
+}
+
+BENCHMARK_RELATIVE(throwWrappedAndCatchContended) {
+  contend(throwWrappedAndCatchImpl);
+}
+
+BENCHMARK_RELATIVE(throwWrappedAndCatchWrappedContended) {
+  contend(throwWrappedAndCatchWrappedImpl);
+}
+
 int main(int argc, char** argv) {
   gflags::ParseCommandLineFlags(&argc, &argv, true);
   folly::runBenchmarks();
index 07f24621da0fa860da7a91a240f3e7412a9796da..939ff2eb4f9453a1bd65953e72251cf3137f3a00 100644 (file)
@@ -500,7 +500,7 @@ TEST(Promise, setException) {
     try {
       throw eggs;
     } catch (...) {
-      p.setException(std::current_exception());
+      p.setException(exception_wrapper(std::current_exception()));
     }
     EXPECT_THROW(f.value(), eggs_t);
   }
index 3ecf050dcb90c2c737e0edb9063b611c48627f49..9204efaf140842c25b52308e267255bbd7058523 100644 (file)
 #include <folly/wangle/futures/Promise.h>
 
 using namespace folly::wangle;
+using folly::exception_wrapper;
 
 TEST(Interrupts, raise) {
   std::runtime_error eggs("eggs");
   Promise<void> p;
-  p.setInterruptHandler([&](std::exception_ptr e) {
-    EXPECT_THROW(std::rethrow_exception(e), decltype(eggs));
+  p.setInterruptHandler([&](const exception_wrapper& e) {
+    EXPECT_THROW(e.throwException(), decltype(eggs));
   });
   p.getFuture().raise(eggs);
 }
 
 TEST(Interrupts, cancel) {
   Promise<void> p;
-  p.setInterruptHandler([&](std::exception_ptr e) {
-    EXPECT_THROW(std::rethrow_exception(e), FutureCancellation);
+  p.setInterruptHandler([&](const exception_wrapper& e) {
+    EXPECT_THROW(e.throwException(), FutureCancellation);
   });
   p.getFuture().cancel();
 }
@@ -41,7 +42,7 @@ TEST(Interrupts, cancel) {
 TEST(Interrupts, handleThenInterrupt) {
   Promise<int> p;
   bool flag = false;
-  p.setInterruptHandler([&](std::exception_ptr e) { flag = true; });
+  p.setInterruptHandler([&](const exception_wrapper& e) { flag = true; });
   p.getFuture().cancel();
   EXPECT_TRUE(flag);
 }
@@ -50,14 +51,14 @@ TEST(Interrupts, interruptThenHandle) {
   Promise<int> p;
   bool flag = false;
   p.getFuture().cancel();
-  p.setInterruptHandler([&](std::exception_ptr e) { flag = true; });
+  p.setInterruptHandler([&](const exception_wrapper& e) { flag = true; });
   EXPECT_TRUE(flag);
 }
 
 TEST(Interrupts, interruptAfterFulfilNoop) {
   Promise<void> p;
   bool flag = false;
-  p.setInterruptHandler([&](std::exception_ptr e) { flag = true; });
+  p.setInterruptHandler([&](const exception_wrapper& e) { flag = true; });
   p.setValue();
   p.getFuture().cancel();
   EXPECT_FALSE(flag);
@@ -66,7 +67,7 @@ TEST(Interrupts, interruptAfterFulfilNoop) {
 TEST(Interrupts, secondInterruptNoop) {
   Promise<void> p;
   int count = 0;
-  p.setInterruptHandler([&](std::exception_ptr e) { count++; });
+  p.setInterruptHandler([&](const exception_wrapper& e) { count++; });
   auto f = p.getFuture();
   f.cancel();
   f.cancel();
index 2c8a60686b3baee8756cb51158ffe1d04f1fb4a5..9b2f4f74d45a0fa11f0fc6d9b7d8517885b4b2e3 100644 (file)
@@ -38,7 +38,7 @@ TEST(Try, makeTryFunctionThrow) {
   };
 
   auto result = makeTryFunction(func);
-  EXPECT_TRUE(result.hasException());
+  EXPECT_TRUE(result.hasException<std::runtime_error>());
 }
 
 TEST(Try, makeTryFunctionVoid) {
@@ -57,5 +57,5 @@ TEST(Try, makeTryFunctionVoidThrow) {
   };
 
   auto result = makeTryFunction(func);
-  EXPECT_TRUE(result.hasException());
+  EXPECT_TRUE(result.hasException<std::runtime_error>());
 }