Commit 19e3e9fe authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

non-throwing, non-allocating exception_wrapper

Summary:
The purpose of this reimplementation of `exception_wrapper` is threefold:

- Make `exception_wrapper` smaller. It goes from 48 bytes to 24.
- Give it `noexcept` ~~copy and~~ move
- Store small exception objects in an internal buffer; i.e., with zero allocations.

The ultimate goal is to change `folly::Try<T>` to a thin wrapper over `folly::Expected<T, exception_wrapper>`. (Currently, it stores the `exception_wrapper` on the heap.)

As part of this redesign, I:

- Remove `exception_wrapper::getCopied`. The user shouldn't care how the `exception_wrapper` stores the exception.
- Remove `exception_wrapper::operator==`. It was only used in 2 places in test code. The existing semantics (return true IFF two `exception_wrapper`s point to the //same// exception object) prevented the small-object optimization.
- Add new `handle()` API that behaves like cascading `catch` clauses. For instance:
```lang=c++
exception_wrapper ew = ...;
ew.handle(
    [&](const SomeException& e) { /*...*/ },
    [&](const AnotherException& e) { /*...*/ },
    [&](...) { /* catch all*/ }, // yes, lambda with ellipses works!
```
- Add a `type()` member for accessing the `typeid` of the wrapped exception, if it's known or can be determined with a `catch(std::exception&)`.

This table shows the percent improvement for the exception_wrapper_benchmark test:

| Test  | Percent improvement (gcc-5)  | Percent improvement (gcc-4)
| -----  | -----  | -----
| exception_wrapper_create_and_test  | 14.33%    | -6.50%
| exception_wrapper_create_and_test_concurrent | 11.91% | 20.15%
| exception_wrapper_create_and_throw | -0.82% | -0.25%
| exception_wrapper_create_and_cast | 15.02% | 14.31%
| exception_wrapper_create_and_throw_concurrent | 18.37% | 8.03%
| exception_wrapper_create_and_cast_concurrent | 28.18% | -10.77%

The percent win for gcc-5 is 15% on average. The non-throwing tests show a greater win since the cost of actually throwing an exception drowns out the other improvements. (One of the reasons to use `exception_wrapper` is to not need to throw in the first place.) On gcc-4, there is roughly no change since the gcc-4 standard exceptions (`std::runtime_error`, std::logic_error`) are non-conforming since they have throwing copy operations.

Reviewed By: yfeldblum

Differential Revision: D4385822

fbshipit-source-id: 63a8316c2923b29a79f8fa446126a8c37aa32989
parent c5b9338e
......@@ -126,3 +126,12 @@
#else
# define FOLLY_ALWAYS_INLINE inline
#endif
// attribute hidden
#if _MSC_VER
#define FOLLY_ATTR_VISIBILITY_HIDDEN
#elif defined(__clang__) || defined(__GNUC__)
#define FOLLY_ATTR_VISIBILITY_HIDDEN __attribute__((__visibility__("hidden")))
#else
#define FOLLY_ATTR_VISIBILITY_HIDDEN
#endif
This diff is collapsed.
......@@ -15,50 +15,53 @@
*/
#include <folly/ExceptionWrapper.h>
#include <exception>
#include <iostream>
#include <folly/Logging.h>
namespace folly {
[[noreturn]] void exception_wrapper::throwException() const {
if (throwfn_) {
throwfn_(*item_);
} else if (eptr_) {
std::rethrow_exception(eptr_);
constexpr exception_wrapper::VTable const exception_wrapper::uninit_;
constexpr exception_wrapper::VTable const exception_wrapper::ExceptionPtr::ops_;
constexpr exception_wrapper::VTable const exception_wrapper::SharedPtr::ops_;
namespace {
std::exception const* get_std_exception_(std::exception_ptr eptr) noexcept {
try {
std::rethrow_exception(eptr);
} catch (const std::exception& ex) {
return &ex;
} catch (...) {
return nullptr;
}
std::ios_base::Init ioinit_; // ensure std::cerr is alive
std::cerr
<< "Cannot use `throwException` with an empty folly::exception_wrapper"
<< std::endl;
std::terminate();
}
}
fbstring exception_wrapper::class_name() const {
if (item_) {
auto& i = *item_;
return demangle(typeid(i));
} else if (eptr_ && eobj_) {
return demangle(typeid(*eobj_));
} else if (eptr_ && etype_) {
return demangle(*etype_);
} else {
return fbstring();
exception_wrapper::exception_wrapper(std::exception_ptr ptr) noexcept
: exception_wrapper{} {
if (ptr) {
if (auto e = get_std_exception_(ptr)) {
LOG(DFATAL)
<< "Performance error: Please construct exception_wrapper with a "
"reference to the std::exception along with the "
"std::exception_ptr.";
*this = exception_wrapper{std::move(ptr), *e};
} else {
Unknown uk;
*this = exception_wrapper{ptr, uk};
}
}
}
fbstring exception_wrapper::what() const {
if (item_) {
return exceptionStr(*item_);
} else if (eptr_ && eobj_) {
return class_name() + ": " + eobj_->what();
} else if (eptr_ && etype_) {
return class_name();
} else {
return class_name();
}
[[noreturn]] void exception_wrapper::onNoExceptionError() {
std::ios_base::Init ioinit_; // ensure std::cerr is alive
std::cerr
<< "Cannot use `throwException` with an empty folly::exception_wrapper"
<< std::endl;
std::terminate();
}
fbstring exceptionStr(const exception_wrapper& ew) {
fbstring exceptionStr(exception_wrapper const& ew) {
return ew.what();
}
......
This diff is collapsed.
......@@ -93,6 +93,7 @@ nobase_follyinclude_HEADERS = \
Exception.h \
ExceptionString.h \
ExceptionWrapper.h \
ExceptionWrapper-inl.h \
Executor.h \
Expected.h \
experimental/AsymmetricMemoryBarrier.h \
......
......@@ -126,8 +126,8 @@ TEST(FutureSplitter, splitFutureFailure) {
EXPECT_FALSE(f1.isReady());
try {
throw std::runtime_error("Oops");
} catch (...) {
p.setException(exception_wrapper(std::current_exception()));
} catch (std::exception& e) {
p.setException(exception_wrapper(std::current_exception(), e));
}
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasException());
......
This diff is collapsed.
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