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 {
*
*/
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);
}
}
}
......
......@@ -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()));
}
}
});
......
......@@ -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(). */
......
......@@ -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));
}
......
......@@ -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();
......
......@@ -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()));
}
}
......
......@@ -19,7 +19,10 @@
#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};
};
/**
......
......@@ -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>
......
......@@ -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();
......
......@@ -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);
}
......
......@@ -20,20 +20,21 @@
#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();
......
......@@ -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>());
}
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