Commit 352bbea6 authored by James Sedgwick's avatar James Sedgwick Committed by Viswanath Sivakumar

exception_ptr -> exception_wrapper migration

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
parent 06cee071
...@@ -102,15 +102,50 @@ namespace folly { ...@@ -102,15 +102,50 @@ namespace folly {
* *
*/ */
class exception_wrapper { class exception_wrapper {
protected:
template <typename Ex>
struct optimize;
public: public:
exception_wrapper() : throwfn_(nullptr) { } exception_wrapper() = default;
// Implicitly construct an exception_wrapper from any std::exception // Implicitly construct an exception_wrapper from a qualifying exception.
template <typename T, typename = // See the optimize struct for details.
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> template <typename Ex, typename =
/* implicit */ exception_wrapper(T&& exn) { typename std::enable_if<optimize<Ex>::value>::type>
item_ = std::make_shared<T>(std::forward<T>(exn)); /* implicit */ exception_wrapper(Ex&& exn) {
throwfn_ = folly::detail::Thrower<T>::doThrow; 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 { void throwException() const {
...@@ -185,14 +220,41 @@ class exception_wrapper { ...@@ -185,14 +220,41 @@ class exception_wrapper {
return false; 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> template <class Ex, class F>
bool with_exception(F f) { typename std::enable_if<
return with_exception1<Ex>(f, this); 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> template <class Ex, class F>
bool with_exception(F f) const { typename std::enable_if<
return with_exception1<const Ex>(f, this); !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 { std::exception_ptr getExceptionPtr() const {
...@@ -209,11 +271,30 @@ class exception_wrapper { ...@@ -209,11 +271,30 @@ class exception_wrapper {
} }
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 // 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 // store a copy of the concrete type, and a helper function so we
// can rethrow it. // can rethrow it.
std::shared_ptr<std::exception> item_; 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 // Fallback case: store the library wrapper, which is less efficient
// but gets the job done. Also store exceptionPtr() the name of the // but gets the job done. Also store exceptionPtr() the name of the
// exception type, so we can at least get those back out without // exception type, so we can at least get those back out without
...@@ -318,29 +399,14 @@ class try_and_catch<LastException, Exceptions...> : ...@@ -318,29 +399,14 @@ class try_and_catch<LastException, Exceptions...> :
try_and_catch() : Base() {} try_and_catch() : Base() {}
template <typename Ex> template <typename Ex>
void assign_eptr(Ex& e) { typename std::enable_if<!exception_wrapper::optimize<Ex>::value>::type
this->eptr_ = std::current_exception(); assign_exception(Ex& e, std::exception_ptr eptr) {
this->estr_ = exceptionStr(e).toStdString(); exception_wrapper::assign_eptr(eptr, e);
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);
} }
template <typename Ex> template <typename Ex>
typename std::enable_if<optimize<Ex>::value>::type typename std::enable_if<exception_wrapper::optimize<Ex>::value>::type
assign_exception(Ex& e) { assign_exception(Ex& e, std::exception_ptr eptr) {
this->item_ = std::make_shared<Ex>(e); this->item_ = std::make_shared<Ex>(e);
this->throwfn_ = folly::detail::Thrower<Ex>::doThrow; this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
} }
...@@ -351,9 +417,9 @@ class try_and_catch<LastException, Exceptions...> : ...@@ -351,9 +417,9 @@ class try_and_catch<LastException, Exceptions...> :
Base::call_fn(std::move(fn)); Base::call_fn(std::move(fn));
} catch (LastException& e) { } catch (LastException& e) {
if (typeid(e) == typeid(LastException&)) { if (typeid(e) == typeid(LastException&)) {
assign_exception(e); assign_exception(e, std::current_exception());
} else { } else {
assign_eptr(e); exception_wrapper::assign_eptr(std::current_exception(), e);
} }
} }
} }
......
...@@ -131,8 +131,8 @@ Future<T>::then(F&& func) { ...@@ -131,8 +131,8 @@ Future<T>::then(F&& func) {
setCallback_( setCallback_(
[p, funcm](Try<T>&& t) mutable { [p, funcm](Try<T>&& t) mutable {
p->fulfil([&]() { p->fulfil([&]() {
return (*funcm)(std::move(t)); return (*funcm)(std::move(t));
}); });
}); });
return std::move(f); return std::move(f);
...@@ -159,7 +159,7 @@ Future<T>::then(F&& func) { ...@@ -159,7 +159,7 @@ Future<T>::then(F&& func) {
setCallback_( setCallback_(
[p, funcm](Try<T>&& t) mutable { [p, funcm](Try<T>&& t) mutable {
if (t.hasException()) { if (t.hasException()) {
p->setException(t.getException()); p->setException(std::move(t.exception()));
} else { } else {
p->fulfil([&]() { p->fulfil([&]() {
return (*funcm)(std::move(t.value())); return (*funcm)(std::move(t.value()));
...@@ -189,7 +189,7 @@ Future<T>::then(F&& func) { ...@@ -189,7 +189,7 @@ Future<T>::then(F&& func) {
setCallback_( setCallback_(
[p, funcm](Try<T>&& t) mutable { [p, funcm](Try<T>&& t) mutable {
if (t.hasException()) { if (t.hasException()) {
p->setException(t.getException()); p->setException(std::move(t.exception()));
} else { } else {
p->fulfil([&]() { p->fulfil([&]() {
return (*funcm)(); return (*funcm)();
...@@ -224,10 +224,12 @@ Future<T>::then(F&& func) { ...@@ -224,10 +224,12 @@ Future<T>::then(F&& func) {
auto f2 = (*funcm)(std::move(t)); auto f2 = (*funcm)(std::move(t));
// that didn't throw, now we can steal p // that didn't throw, now we can steal p
f2.setCallback_([p](Try<B>&& b) mutable { 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 (...) { } catch (...) {
p->setException(std::current_exception()); p->setException(exception_wrapper(std::current_exception()));
} }
}); });
...@@ -255,15 +257,17 @@ Future<T>::then(F&& func) { ...@@ -255,15 +257,17 @@ Future<T>::then(F&& func) {
setCallback_( setCallback_(
[p, funcm](Try<T>&& t) mutable { [p, funcm](Try<T>&& t) mutable {
if (t.hasException()) { if (t.hasException()) {
p->setException(t.getException()); p->setException(std::move(t.exception()));
} else { } else {
try { try {
auto f2 = (*funcm)(std::move(t.value())); auto f2 = (*funcm)(std::move(t.value()));
f2.setCallback_([p](Try<B>&& b) mutable { 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 (...) { } catch (...) {
p->setException(std::current_exception()); p->setException(exception_wrapper(std::current_exception()));
} }
} }
}); });
...@@ -291,15 +295,17 @@ Future<T>::then(F&& func) { ...@@ -291,15 +295,17 @@ Future<T>::then(F&& func) {
setCallback_( setCallback_(
[p, funcm](Try<T>&& t) mutable { [p, funcm](Try<T>&& t) mutable {
if (t.hasException()) { if (t.hasException()) {
p->setException(t.getException()); p->setException(t.exception());
} else { } else {
try { try {
auto f2 = (*funcm)(); auto f2 = (*funcm)();
f2.setCallback_([p](Try<B>&& b) mutable { 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 (...) { } catch (...) {
p->setException(std::current_exception()); p->setException(exception_wrapper(std::current_exception()));
} }
} }
}); });
...@@ -329,17 +335,13 @@ Future<T>::onError(F&& func) { ...@@ -329,17 +335,13 @@ Future<T>::onError(F&& func) {
auto pm = folly::makeMoveWrapper(std::move(p)); auto pm = folly::makeMoveWrapper(std::move(p));
auto funcm = folly::makeMoveWrapper(std::move(func)); auto funcm = folly::makeMoveWrapper(std::move(func));
setCallback_([pm, funcm](Try<T>&& t) mutable { setCallback_([pm, funcm](Try<T>&& t) mutable {
try { if (!t.template withException<Exn>([&] (Exn& e) {
t.throwIfFailed(); pm->fulfil([&]{
} catch (Exn& e) { return (*funcm)(e);
pm->fulfil([&]{ });
return (*funcm)(e); })) {
}); pm->fulfilTry(std::move(t));
return;
} catch (...) {
// fall through
} }
pm->fulfilTry(std::move(t));
}); });
return f; return f;
...@@ -362,22 +364,20 @@ Future<T>::onError(F&& func) { ...@@ -362,22 +364,20 @@ Future<T>::onError(F&& func) {
auto pm = folly::makeMoveWrapper(std::move(p)); auto pm = folly::makeMoveWrapper(std::move(p));
auto funcm = folly::makeMoveWrapper(std::move(func)); auto funcm = folly::makeMoveWrapper(std::move(func));
setCallback_([pm, funcm](Try<T>&& t) mutable { setCallback_([pm, funcm](Try<T>&& t) mutable {
try { if (!t.template withException<Exn>([&] (Exn& e) {
t.throwIfFailed(); try {
} catch (Exn& e) { auto f2 = (*funcm)(e);
try { f2.setCallback_([pm](Try<T>&& t2) mutable {
auto f2 = (*funcm)(e); pm->fulfilTry(std::move(t2));
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 (...) { } catch (...) {
pm->setException(std::current_exception()); pm->setException(exception_wrapper(std::current_exception()));
} }
return; })) {
} catch (...) { pm->fulfilTry(std::move(t));
// fall through
} }
pm->fulfilTry(std::move(t));
}); });
return f; return f;
...@@ -433,8 +433,8 @@ bool Future<T>::isReady() const { ...@@ -433,8 +433,8 @@ bool Future<T>::isReady() const {
} }
template <class T> template <class T>
void Future<T>::raise(std::exception_ptr exception) { void Future<T>::raise(exception_wrapper exception) {
core_->raise(exception); core_->raise(std::move(exception));
} }
// makeFuture // makeFuture
...@@ -483,32 +483,38 @@ Future<T> makeFuture(std::exception_ptr const& e) { ...@@ -483,32 +483,38 @@ Future<T> makeFuture(std::exception_ptr const& e) {
return std::move(f); 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> template <class T, class E>
typename std::enable_if<std::is_base_of<std::exception, E>::value, typename std::enable_if<std::is_base_of<std::exception, E>::value,
Future<T>>::type Future<T>>::type
makeFuture(E const& e) { makeFuture(E const& e) {
Promise<T> p; Promise<T> p;
auto f = p.getFuture(); auto f = p.getFuture();
p.fulfil([&]() -> T { throw e; }); p.setException(make_exception_wrapper<E>(e));
return std::move(f); return std::move(f);
} }
template <class T> template <class T>
Future<T> makeFuture(Try<T>&& 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())); return makeFuture<T>(std::move(t.value()));
} catch (...) {
return makeFuture<T>(std::current_exception());
} }
} }
template <> template <>
inline Future<void> makeFuture(Try<void>&& t) { inline Future<void> makeFuture(Try<void>&& t) {
try { if (t.hasException()) {
t.throwIfFailed(); return makeFuture<void>(std::move(t.exception()));
} else {
return makeFuture(); return makeFuture();
} catch (...) {
return makeFuture<void>(std::current_exception());
} }
} }
...@@ -730,7 +736,9 @@ namespace { ...@@ -730,7 +736,9 @@ namespace {
t.value(); t.value();
p.setException(TimedOut()); p.setException(TimedOut());
} catch (std::exception const& e) { } 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(); baton.post();
} }
...@@ -803,8 +811,12 @@ Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) { ...@@ -803,8 +811,12 @@ Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) {
try { try {
t.throwIfFailed(); t.throwIfFailed();
ctx->promise.setException(std::move(ctx->exception)); ctx->promise.setException(std::move(ctx->exception));
} catch (std::exception const&) { } catch (std::exception const& e) {
ctx->promise.setException(std::current_exception()); ctx->promise.setException(
exception_wrapper(std::current_exception(), e));
} catch (...) {
ctx->promise.setException(
exception_wrapper(std::current_exception()));
} }
} }
}); });
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <vector> #include <vector>
#include <folly/MoveWrapper.h> #include <folly/MoveWrapper.h>
#include <folly/wangle/futures/Deprecated.h>
#include <folly/wangle/futures/Promise.h> #include <folly/wangle/futures/Promise.h>
#include <folly/wangle/futures/Try.h> #include <folly/wangle/futures/Try.h>
#include <folly/wangle/futures/WangleException.h> #include <folly/wangle/futures/WangleException.h>
...@@ -445,7 +446,8 @@ class Future { ...@@ -445,7 +446,8 @@ class Future {
template <class E> template <class E>
void raise(E&& exception) { 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 /// Raise an interrupt. If the promise holder has an interrupt
...@@ -456,7 +458,7 @@ class Future { ...@@ -456,7 +458,7 @@ class Future {
/// preventing the asynchronous operation (if in time), and the promise /// preventing the asynchronous operation (if in time), and the promise
/// holder setting an exception on the future. (That may happen /// holder setting an exception on the future. (That may happen
/// asynchronously, of course.) /// asynchronously, of course.)
void raise(std::exception_ptr interrupt); void raise(exception_wrapper interrupt);
void cancel() { void cancel() {
raise(FutureCancellation()); raise(FutureCancellation());
...@@ -527,7 +529,11 @@ auto makeFutureTry( ...@@ -527,7 +529,11 @@ auto makeFutureTry(
/// ///
/// auto f = makeFuture<string>(std::current_exception()); /// auto f = makeFuture<string>(std::current_exception());
template <class T> 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 /** Make a Future from an exception type E that can be passed to
std::make_exception_ptr(). */ std::make_exception_ptr(). */
......
...@@ -78,19 +78,31 @@ Future<T> Promise<T>::getFuture() { ...@@ -78,19 +78,31 @@ Future<T> Promise<T>::getFuture() {
template <class T> template <class T>
template <class E> template <class E>
void Promise<T>::setException(E const& e) { typename std::enable_if<std::is_base_of<std::exception, E>::value>::type
setException(std::make_exception_ptr<E>(e)); Promise<T>::setException(E const& e) {
setException(make_exception_wrapper<E>(e));
} }
template <class T> template <class T>
void Promise<T>::setException(std::exception_ptr const& e) { 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(); throwIfFulfilled();
core_->setResult(Try<T>(e)); core_->setResult(Try<T>(std::move(ew)));
} }
template <class T> template <class T>
void Promise<T>::setInterruptHandler( void Promise<T>::setInterruptHandler(
std::function<void(std::exception_ptr const&)> fn) { std::function<void(exception_wrapper const&)> fn) {
core_->setInterruptHandler(std::move(fn)); core_->setInterruptHandler(std::move(fn));
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#pragma once #pragma once
#include <folly/wangle/futures/Deprecated.h>
#include <folly/wangle/futures/Try.h> #include <folly/wangle/futures/Try.h>
#include <functional> #include <functional>
...@@ -42,6 +43,9 @@ public: ...@@ -42,6 +43,9 @@ public:
once, thereafter Future already retrieved exception will be raised. */ once, thereafter Future already retrieved exception will be raised. */
Future<T> getFuture(); Future<T> getFuture();
/** Fulfil the Promise with an exception_wrapper */
void setException(exception_wrapper ew);
/** Fulfil the Promise with an exception_ptr, e.g. /** Fulfil the Promise with an exception_ptr, e.g.
try { try {
... ...
...@@ -49,20 +53,22 @@ public: ...@@ -49,20 +53,22 @@ public:
p.setException(std::current_exception()); 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 /** Fulfil the Promise with an exception type E, which can be passed to
std::make_exception_ptr(). Useful for originating exceptions. If you 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 /// Set an interrupt handler to handle interrupts. See the documentation for
/// Future::raise(). Your handler can do whatever it wants, but if you /// 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 /// bother to set one then you probably will want to fulfil the promise with
/// an exception (or special value) indicating how the interrupt was /// an exception (or special value) indicating how the interrupt was
/// handled. /// 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>) */ /** Fulfil this Promise (only for Promise<void>) */
void setValue(); void setValue();
......
...@@ -27,7 +27,7 @@ Try<T>::Try(Try<T>&& t) : contains_(t.contains_) { ...@@ -27,7 +27,7 @@ Try<T>::Try(Try<T>&& t) : contains_(t.contains_) {
if (contains_ == Contains::VALUE) { if (contains_ == Contains::VALUE) {
new (&value_)T(std::move(t.value_)); new (&value_)T(std::move(t.value_));
} else if (contains_ == Contains::EXCEPTION) { } 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) { ...@@ -38,7 +38,7 @@ Try<T>& Try<T>::operator=(Try<T>&& t) {
if (contains_ == Contains::VALUE) { if (contains_ == Contains::VALUE) {
new (&value_)T(std::move(t.value_)); new (&value_)T(std::move(t.value_));
} else if (contains_ == Contains::EXCEPTION) { } 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; return *this;
} }
...@@ -48,7 +48,7 @@ Try<T>::~Try() { ...@@ -48,7 +48,7 @@ Try<T>::~Try() {
if (contains_ == Contains::VALUE) { if (contains_ == Contains::VALUE) {
value_.~T(); value_.~T();
} else if (contains_ == Contains::EXCEPTION) { } else if (contains_ == Contains::EXCEPTION) {
e_.~exception_ptr(); e_.~unique_ptr<exception_wrapper>();
} }
} }
...@@ -68,7 +68,7 @@ template <class T> ...@@ -68,7 +68,7 @@ template <class T>
void Try<T>::throwIfFailed() const { void Try<T>::throwIfFailed() const {
if (contains_ != Contains::VALUE) { if (contains_ != Contains::VALUE) {
if (contains_ == Contains::EXCEPTION) { if (contains_ == Contains::EXCEPTION) {
std::rethrow_exception(e_); e_->throwException();
} else { } else {
throw UsingUninitializedTry(); throw UsingUninitializedTry();
} }
...@@ -77,7 +77,7 @@ void Try<T>::throwIfFailed() const { ...@@ -77,7 +77,7 @@ void Try<T>::throwIfFailed() const {
void Try<void>::throwIfFailed() const { void Try<void>::throwIfFailed() const {
if (!hasValue_) { if (!hasValue_) {
std::rethrow_exception(e_); e_->throwException();
} }
} }
...@@ -97,10 +97,11 @@ typename std::enable_if< ...@@ -97,10 +97,11 @@ typename std::enable_if<
makeTryFunction(F&& f) { makeTryFunction(F&& f) {
typedef typename std::result_of<F()>::type ResultType; typedef typename std::result_of<F()>::type ResultType;
try { try {
auto value = f(); return Try<ResultType>(f());
return Try<ResultType>(std::move(value)); } catch (std::exception& e) {
return Try<ResultType>(exception_wrapper(std::current_exception(), e));
} catch (...) { } catch (...) {
return Try<ResultType>(std::current_exception()); return Try<ResultType>(exception_wrapper(std::current_exception()));
} }
} }
...@@ -112,8 +113,10 @@ makeTryFunction(F&& f) { ...@@ -112,8 +113,10 @@ makeTryFunction(F&& f) {
try { try {
f(); f();
return Try<void>(); return Try<void>();
} catch (std::exception& e) {
return Try<void>(exception_wrapper(std::current_exception(), e));
} catch (...) { } catch (...) {
return Try<void>(std::current_exception()); return Try<void>(exception_wrapper(std::current_exception()));
} }
} }
......
...@@ -19,7 +19,10 @@ ...@@ -19,7 +19,10 @@
#include <type_traits> #include <type_traits>
#include <exception> #include <exception>
#include <algorithm> #include <algorithm>
#include <folly/ExceptionWrapper.h>
#include <folly/Likely.h> #include <folly/Likely.h>
#include <folly/Memory.h>
#include <folly/wangle/futures/Deprecated.h>
#include <folly/wangle/futures/WangleException.h> #include <folly/wangle/futures/WangleException.h>
namespace folly { namespace wangle { namespace folly { namespace wangle {
...@@ -41,7 +44,19 @@ class Try { ...@@ -41,7 +44,19 @@ class Try {
Try() : contains_(Contains::NOTHING) {} Try() : contains_(Contains::NOTHING) {}
explicit Try(const T& v) : contains_(Contains::VALUE), value_(v) {} explicit Try(const T& v) : contains_(Contains::VALUE), value_(v) {}
explicit Try(T&& v) : contains_(Contains::VALUE), value_(std::move(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 // move
Try(Try<T>&& t); Try(Try<T>&& t);
...@@ -67,19 +82,31 @@ class Try { ...@@ -67,19 +82,31 @@ class Try {
bool hasValue() const { return contains_ == Contains::VALUE; } bool hasValue() const { return contains_ == Contains::VALUE; }
bool hasException() const { return contains_ == Contains::EXCEPTION; } 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())) { if (UNLIKELY(!hasException())) {
throw WangleException( throw WangleException("exception(): Try does not contain an exception");
"getException(): 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: private:
Contains contains_; Contains contains_;
union { union {
T value_; T value_;
std::exception_ptr e_; std::unique_ptr<exception_wrapper> e_;
}; };
}; };
...@@ -87,7 +114,29 @@ template <> ...@@ -87,7 +114,29 @@ template <>
class Try<void> { class Try<void> {
public: public:
Try() : hasValue_(true) {} 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 value() const { throwIfFailed(); }
void operator*() const { return value(); } void operator*() const { return value(); }
...@@ -97,17 +146,29 @@ class Try<void> { ...@@ -97,17 +146,29 @@ class Try<void> {
bool hasValue() const { return hasValue_; } bool hasValue() const { return hasValue_; }
bool hasException() 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())) { if (UNLIKELY(!hasException())) {
throw WangleException( throw WangleException("exception(): Try does not contain an exception");
"getException(): 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: private:
bool hasValue_; bool hasValue_;
std::exception_ptr e_; std::unique_ptr<exception_wrapper> e_{nullptr};
}; };
/** /**
......
...@@ -132,7 +132,7 @@ class Core : protected FSM<State> { ...@@ -132,7 +132,7 @@ class Core : protected FSM<State> {
// Called by a destructing Promise // Called by a destructing Promise
void detachPromise() { void detachPromise() {
if (!ready()) { if (!ready()) {
setResult(Try<T>(std::make_exception_ptr(BrokenPromise()))); setResult(Try<T>(exception_wrapper(BrokenPromise())));
} }
detachOne(); detachOne();
} }
...@@ -154,18 +154,18 @@ class Core : protected FSM<State> { ...@@ -154,18 +154,18 @@ class Core : protected FSM<State> {
executor_ = x; executor_ = x;
} }
void raise(std::exception_ptr const& e) { void raise(exception_wrapper const& e) {
FSM_START FSM_START
case State::Interruptible: case State::Interruptible:
FSM_UPDATE2(State::Interrupted, FSM_UPDATE2(State::Interrupted,
[&]{ interrupt_ = e; }, [&]{ interrupt_ = folly::make_unique<exception_wrapper>(e); },
[&]{ interruptHandler_(interrupt_); }); [&]{ interruptHandler_(*interrupt_); });
break; break;
case State::Waiting: case State::Waiting:
case State::Interrupted: case State::Interrupted:
FSM_UPDATE(State::Interrupted, FSM_UPDATE(State::Interrupted,
[&]{ interrupt_ = e; }); [&]{ interrupt_ = folly::make_unique<exception_wrapper>(e); });
break; break;
case State::Done: case State::Done:
...@@ -173,7 +173,7 @@ class Core : protected FSM<State> { ...@@ -173,7 +173,7 @@ class Core : protected FSM<State> {
FSM_END FSM_END
} }
void setInterruptHandler(std::function<void(std::exception_ptr const&)> fn) { void setInterruptHandler(std::function<void(exception_wrapper const&)> fn) {
FSM_START FSM_START
case State::Waiting: case State::Waiting:
case State::Interruptible: case State::Interruptible:
...@@ -182,7 +182,7 @@ class Core : protected FSM<State> { ...@@ -182,7 +182,7 @@ class Core : protected FSM<State> {
break; break;
case State::Interrupted: case State::Interrupted:
fn(interrupt_); fn(*interrupt_);
FSM_BREAK FSM_BREAK
case State::Done: case State::Done:
...@@ -228,8 +228,8 @@ class Core : protected FSM<State> { ...@@ -228,8 +228,8 @@ class Core : protected FSM<State> {
std::atomic<unsigned char> detached_ {0}; std::atomic<unsigned char> detached_ {0};
std::atomic<bool> active_ {true}; std::atomic<bool> active_ {true};
std::atomic<Executor*> executor_ {nullptr}; std::atomic<Executor*> executor_ {nullptr};
std::exception_ptr interrupt_; std::unique_ptr<exception_wrapper> interrupt_;
std::function<void(std::exception_ptr const&)> interruptHandler_; std::function<void(exception_wrapper const&)> interruptHandler_;
}; };
template <typename... Ts> template <typename... Ts>
......
...@@ -165,6 +165,132 @@ BENCHMARK_RELATIVE(contention) { ...@@ -165,6 +165,132 @@ BENCHMARK_RELATIVE(contention) {
producer.join(); 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) { int main(int argc, char** argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true); gflags::ParseCommandLineFlags(&argc, &argv, true);
folly::runBenchmarks(); folly::runBenchmarks();
......
...@@ -500,7 +500,7 @@ TEST(Promise, setException) { ...@@ -500,7 +500,7 @@ TEST(Promise, setException) {
try { try {
throw eggs; throw eggs;
} catch (...) { } catch (...) {
p.setException(std::current_exception()); p.setException(exception_wrapper(std::current_exception()));
} }
EXPECT_THROW(f.value(), eggs_t); EXPECT_THROW(f.value(), eggs_t);
} }
......
...@@ -20,20 +20,21 @@ ...@@ -20,20 +20,21 @@
#include <folly/wangle/futures/Promise.h> #include <folly/wangle/futures/Promise.h>
using namespace folly::wangle; using namespace folly::wangle;
using folly::exception_wrapper;
TEST(Interrupts, raise) { TEST(Interrupts, raise) {
std::runtime_error eggs("eggs"); std::runtime_error eggs("eggs");
Promise<void> p; Promise<void> p;
p.setInterruptHandler([&](std::exception_ptr e) { p.setInterruptHandler([&](const exception_wrapper& e) {
EXPECT_THROW(std::rethrow_exception(e), decltype(eggs)); EXPECT_THROW(e.throwException(), decltype(eggs));
}); });
p.getFuture().raise(eggs); p.getFuture().raise(eggs);
} }
TEST(Interrupts, cancel) { TEST(Interrupts, cancel) {
Promise<void> p; Promise<void> p;
p.setInterruptHandler([&](std::exception_ptr e) { p.setInterruptHandler([&](const exception_wrapper& e) {
EXPECT_THROW(std::rethrow_exception(e), FutureCancellation); EXPECT_THROW(e.throwException(), FutureCancellation);
}); });
p.getFuture().cancel(); p.getFuture().cancel();
} }
...@@ -41,7 +42,7 @@ TEST(Interrupts, cancel) { ...@@ -41,7 +42,7 @@ TEST(Interrupts, cancel) {
TEST(Interrupts, handleThenInterrupt) { TEST(Interrupts, handleThenInterrupt) {
Promise<int> p; Promise<int> p;
bool flag = false; bool flag = false;
p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); p.setInterruptHandler([&](const exception_wrapper& e) { flag = true; });
p.getFuture().cancel(); p.getFuture().cancel();
EXPECT_TRUE(flag); EXPECT_TRUE(flag);
} }
...@@ -50,14 +51,14 @@ TEST(Interrupts, interruptThenHandle) { ...@@ -50,14 +51,14 @@ TEST(Interrupts, interruptThenHandle) {
Promise<int> p; Promise<int> p;
bool flag = false; bool flag = false;
p.getFuture().cancel(); p.getFuture().cancel();
p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); p.setInterruptHandler([&](const exception_wrapper& e) { flag = true; });
EXPECT_TRUE(flag); EXPECT_TRUE(flag);
} }
TEST(Interrupts, interruptAfterFulfilNoop) { TEST(Interrupts, interruptAfterFulfilNoop) {
Promise<void> p; Promise<void> p;
bool flag = false; bool flag = false;
p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); p.setInterruptHandler([&](const exception_wrapper& e) { flag = true; });
p.setValue(); p.setValue();
p.getFuture().cancel(); p.getFuture().cancel();
EXPECT_FALSE(flag); EXPECT_FALSE(flag);
...@@ -66,7 +67,7 @@ TEST(Interrupts, interruptAfterFulfilNoop) { ...@@ -66,7 +67,7 @@ TEST(Interrupts, interruptAfterFulfilNoop) {
TEST(Interrupts, secondInterruptNoop) { TEST(Interrupts, secondInterruptNoop) {
Promise<void> p; Promise<void> p;
int count = 0; int count = 0;
p.setInterruptHandler([&](std::exception_ptr e) { count++; }); p.setInterruptHandler([&](const exception_wrapper& e) { count++; });
auto f = p.getFuture(); auto f = p.getFuture();
f.cancel(); f.cancel();
f.cancel(); f.cancel();
......
...@@ -38,7 +38,7 @@ TEST(Try, makeTryFunctionThrow) { ...@@ -38,7 +38,7 @@ TEST(Try, makeTryFunctionThrow) {
}; };
auto result = makeTryFunction(func); auto result = makeTryFunction(func);
EXPECT_TRUE(result.hasException()); EXPECT_TRUE(result.hasException<std::runtime_error>());
} }
TEST(Try, makeTryFunctionVoid) { TEST(Try, makeTryFunctionVoid) {
...@@ -57,5 +57,5 @@ TEST(Try, makeTryFunctionVoidThrow) { ...@@ -57,5 +57,5 @@ TEST(Try, makeTryFunctionVoidThrow) {
}; };
auto result = makeTryFunction(func); auto result = makeTryFunction(func);
EXPECT_TRUE(result.hasException()); EXPECT_TRUE(result.hasException<std::runtime_error>());
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment