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