From: Eric Niebler Date: Tue, 11 Apr 2017 23:12:59 +0000 (-0700) Subject: non-throwing, non-allocating exception_wrapper X-Git-Tag: v2017.04.17.00~36 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=19e3e9fe724a363e342e92a5b08378900d6ab539 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` to a thin wrapper over `folly::Expected`. (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 --- diff --git a/folly/CPortability.h b/folly/CPortability.h index 0989cbc9..50984477 100644 --- a/folly/CPortability.h +++ b/folly/CPortability.h @@ -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 diff --git a/folly/ExceptionWrapper-inl.h b/folly/ExceptionWrapper-inl.h new file mode 100644 index 00000000..17548445 --- /dev/null +++ b/folly/ExceptionWrapper-inl.h @@ -0,0 +1,584 @@ +/* + * Copyright 2017-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * + * Author: Eric Niebler + */ + +namespace folly { + +template +struct exception_wrapper::arg_type2_ {}; +template +struct exception_wrapper::arg_type2_ { + using type = Arg; +}; +template +struct exception_wrapper::arg_type2_ { + using type = Arg; +}; +template +struct exception_wrapper::arg_type2_ { + using type = AnyException; +}; +template +struct exception_wrapper::arg_type2_ { + using type = AnyException; +}; + +template +struct exception_wrapper::arg_type_ {}; +template +struct exception_wrapper::arg_type_> + : public arg_type2_ {}; +template +struct exception_wrapper::arg_type_ { + using type = Arg; +}; +template +struct exception_wrapper::arg_type_ { + using type = AnyException; +}; + +template +inline Ret exception_wrapper::noop_(Args...) { + return Ret(); +} + +inline std::type_info const* exception_wrapper::uninit_type_( + exception_wrapper const*) { + return &typeid(void); +} + +template +inline exception_wrapper::Buffer::Buffer(in_place_t, Ex&& ex) { + ::new (static_cast(&buff_)) DEx(std::forward(ex)); +} + +template +inline Ex& exception_wrapper::Buffer::as() noexcept { + return *static_cast(static_cast(&buff_)); +} +template +inline Ex const& exception_wrapper::Buffer::as() const noexcept { + return *static_cast(static_cast(&buff_)); +} + +inline std::exception const* exception_wrapper::as_exception_or_null_( + std::exception const& ex) { + return &ex; +} +inline std::exception const* exception_wrapper::as_exception_or_null_( + AnyException) { + return nullptr; +} + +inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_( + std::exception const& e) { + return reinterpret_cast(&e); +} +inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(AnyException e) { + return reinterpret_cast(e.typeinfo_) + 1; +} +inline bool exception_wrapper::ExceptionPtr::has_exception_() const { + return 0 == exception_or_type_ % 2; +} +inline std::exception const* exception_wrapper::ExceptionPtr::as_exception_() + const { + return reinterpret_cast(exception_or_type_); +} +inline std::type_info const* exception_wrapper::ExceptionPtr::as_type_() const { + return reinterpret_cast(exception_or_type_ - 1); +} + +inline void exception_wrapper::ExceptionPtr::copy_( + exception_wrapper const* from, exception_wrapper* to) { + ::new (static_cast(&to->eptr_)) ExceptionPtr(from->eptr_); +} +inline void exception_wrapper::ExceptionPtr::move_( + exception_wrapper* from, exception_wrapper* to) { + ::new (static_cast(&to->eptr_)) + ExceptionPtr(std::move(from->eptr_)); + delete_(from); +} +inline void exception_wrapper::ExceptionPtr::delete_( + exception_wrapper* that) { + that->eptr_.~ExceptionPtr(); + that->vptr_ = &uninit_; +} +[[noreturn]] inline void exception_wrapper::ExceptionPtr::throw_( + exception_wrapper const* that) { + std::rethrow_exception(that->eptr_.ptr_); +} +inline std::type_info const* exception_wrapper::ExceptionPtr::type_( + exception_wrapper const* that) { + if (auto e = get_exception_(that)) { + return &typeid(*e); + } + return that->eptr_.as_type_(); +} +inline std::exception const* exception_wrapper::ExceptionPtr::get_exception_( + exception_wrapper const* that) { + return that->eptr_.has_exception_() ? that->eptr_.as_exception_() + : nullptr; +} +inline exception_wrapper exception_wrapper::ExceptionPtr::get_exception_ptr_( + exception_wrapper const* that) { + return *that; +} + +template +inline void exception_wrapper::InPlace::copy_( + exception_wrapper const* from, exception_wrapper* to) { + ::new (static_cast(std::addressof(to->buff_.as()))) + Ex(from->buff_.as()); +} +template +inline void exception_wrapper::InPlace::move_( + exception_wrapper* from, exception_wrapper* to) { + ::new (static_cast(std::addressof(to->buff_.as()))) + Ex(std::move(from->buff_.as())); + delete_(from); +} +template +inline void exception_wrapper::InPlace::delete_( + exception_wrapper* that) { + that->buff_.as().~Ex(); + that->vptr_ = &uninit_; +} +template +[[noreturn]] inline void exception_wrapper::InPlace::throw_( + exception_wrapper const* that) { + throw that->buff_.as(); // @nolint +} +template +inline std::type_info const* exception_wrapper::InPlace::type_( + exception_wrapper const*) { + return &typeid(Ex); +} +template +inline std::exception const* exception_wrapper::InPlace::get_exception_( + exception_wrapper const* that) { + return as_exception_or_null_(that->buff_.as()); +} +template +inline exception_wrapper exception_wrapper::InPlace::get_exception_ptr_( + exception_wrapper const* that) { + try { + throw_(that); + } catch (Ex const& ex) { + return exception_wrapper{std::current_exception(), ex}; + } +} + +template +[[noreturn]] inline void +exception_wrapper::SharedPtr::Impl::throw_() const { + throw ex_; // @nolint +} +template +inline std::exception const* +exception_wrapper::SharedPtr::Impl::get_exception_() const noexcept { + return as_exception_or_null_(ex_); +} +template +inline exception_wrapper +exception_wrapper::SharedPtr::Impl::get_exception_ptr_() const noexcept { + try { + throw_(); + } catch (Ex& ex) { + return exception_wrapper{std::current_exception(), ex}; + } +} +inline void exception_wrapper::SharedPtr::copy_( + exception_wrapper const* from, exception_wrapper* to) { + ::new (static_cast(std::addressof(to->sptr_))) + SharedPtr(from->sptr_); +} +inline void exception_wrapper::SharedPtr::move_( + exception_wrapper* from, exception_wrapper* to) { + ::new (static_cast(std::addressof(to->sptr_))) + SharedPtr(std::move(from->sptr_)); + delete_(from); +} +inline void exception_wrapper::SharedPtr::delete_( + exception_wrapper* that) { + that->sptr_.~SharedPtr(); + that->vptr_ = &uninit_; +} +[[noreturn]] inline void exception_wrapper::SharedPtr::throw_( + exception_wrapper const* that) { + that->sptr_.ptr_->throw_(); + folly::assume_unreachable(); +} +inline std::type_info const* exception_wrapper::SharedPtr::type_( + exception_wrapper const* that) { + return that->sptr_.ptr_->info_; +} +inline std::exception const* exception_wrapper::SharedPtr::get_exception_( + exception_wrapper const* that) { + return that->sptr_.ptr_->get_exception_(); +} +inline exception_wrapper exception_wrapper::SharedPtr::get_exception_ptr_( + exception_wrapper const* that) { + return that->sptr_.ptr_->get_exception_ptr_(); +} + +template +inline exception_wrapper::exception_wrapper(Ex&& ex, OnHeapTag) + : sptr_{std::make_shared>(std::forward(ex))}, + vptr_(&SharedPtr::ops_) {} + +template +inline exception_wrapper::exception_wrapper(Ex&& ex, InSituTag) + : buff_{in_place, std::forward(ex)}, vptr_(&InPlace::ops_) {} + +inline exception_wrapper::exception_wrapper(exception_wrapper&& that) noexcept + : exception_wrapper{} { + (vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw +} + +inline exception_wrapper::exception_wrapper( + exception_wrapper const& that) : exception_wrapper{} { + that.vptr_->copy_(&that, this); // could throw + vptr_ = that.vptr_; +} + +// If `this == &that`, this move assignment operator leaves the object in a +// valid but unspecified state. +inline exception_wrapper& exception_wrapper::operator=( + exception_wrapper&& that) noexcept { + vptr_->delete_(this); // Free the current exception + (vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw + return *this; +} + +inline exception_wrapper& exception_wrapper::operator=( + exception_wrapper const& that) { + exception_wrapper(that).swap(*this); + return *this; +} + +inline exception_wrapper::~exception_wrapper() { + reset(); +} + +template +inline exception_wrapper::exception_wrapper(std::exception_ptr ptr, Ex& ex) + : eptr_{std::move(ptr), ExceptionPtr::as_int_(ex)}, + vptr_(&ExceptionPtr::ops_) { + assert(eptr_.ptr_); +} + +template < + class Ex, + class Ex_, + FOLLY_REQUIRES_DEF( + Conjunction< + exception_wrapper::IsStdException, + exception_wrapper::IsRegularExceptionType>())> +inline exception_wrapper::exception_wrapper(Ex&& ex) + : exception_wrapper{std::forward(ex), PlacementOf{}} { + // Don't slice!!! + assert(typeid(ex) == typeid(Ex_) || + !"Dynamic and static exception types don't match. Exception would " + "be sliced when storing in exception_wrapper."); +} + +template < + class Ex, + class Ex_, + FOLLY_REQUIRES_DEF( + exception_wrapper::IsRegularExceptionType())> +inline exception_wrapper::exception_wrapper(in_place_t, Ex&& ex) + : exception_wrapper{std::forward(ex), PlacementOf{}} { + // Don't slice!!! + assert(typeid(ex) == typeid(Ex_) || + !"Dynamic and static exception types don't match. Exception would " + "be sliced when storing in exception_wrapper."); +} + +inline void exception_wrapper::swap(exception_wrapper& that) noexcept { + exception_wrapper tmp(std::move(that)); + that = std::move(*this); + *this = std::move(tmp); +} + +inline exception_wrapper::operator bool() const noexcept { + return vptr_ != &uninit_; +} + +inline bool exception_wrapper::operator!() const noexcept { + return !static_cast(*this); +} + +inline void exception_wrapper::reset() { + vptr_->delete_(this); +} + +inline bool exception_wrapper::has_exception_ptr() const noexcept { + return vptr_ == &ExceptionPtr::ops_; +} + +inline std::exception* exception_wrapper::get_exception() noexcept { + return const_cast(vptr_->get_exception_(this)); +} +inline std::exception const* exception_wrapper::get_exception() const noexcept { + return vptr_->get_exception_(this); +} + +inline std::exception_ptr const& exception_wrapper::to_exception_ptr() + noexcept { + // Computing an exception_ptr is expensive so cache the result. + return (*this = vptr_->get_exception_ptr_(this)).eptr_.ptr_; +} +inline std::exception_ptr exception_wrapper::to_exception_ptr() const noexcept { + return vptr_->get_exception_ptr_(this).eptr_.ptr_; +} + +inline std::type_info const& exception_wrapper::none() noexcept { + return typeid(void); +} +inline std::type_info const& exception_wrapper::unknown() noexcept { + return typeid(Unknown); +} + +inline std::type_info const& exception_wrapper::type() const noexcept { + return *vptr_->type_(this); +} + +inline folly::fbstring exception_wrapper::what() const { + if (auto e = get_exception()) { + return class_name() + ": " + e->what(); + } + return class_name(); +} + +inline folly::fbstring exception_wrapper::class_name() const { + auto& ti = type(); + return ti == none() + ? "" + : ti == unknown() ? "" : folly::demangle(ti); +} + +template +inline bool exception_wrapper::is_compatible_with() const noexcept { + return with_exception([](Ex const&) {}); +} + +[[noreturn]] inline void exception_wrapper::throwException() const { + vptr_->throw_(this); + onNoExceptionError(); +} + +template +struct exception_wrapper::ExceptionTypeOf { + using type = arg_type<_t>>; + static_assert( + std::is_reference::value, + "Always catch exceptions by reference."); + static_assert( + !IsConst || std::is_const<_t>>::value, + "handle() or with_exception() called on a const exception_wrapper " + "and asked to catch a non-const exception. Handler will never fire. " + "Catch exception by const reference to fix this."); +}; + +// Nests a throw in the proper try/catch blocks +template +struct exception_wrapper::HandleReduce { + bool* handled_; + + template < + class ThrowFn, + class CatchFn, + FOLLY_REQUIRES(!IsCatchAll::value)> + auto operator()(ThrowFn&& th, CatchFn& ca) const { + using Ex = _t>; + return [ th = std::forward(th), &ca, handled_ = handled_ ] { + try { + th(); + } catch (Ex& e) { + // If we got here because a catch function threw, rethrow. + if (*handled_) { + throw; + } + *handled_ = true; + ca(e); + } + }; + } + + template < + class ThrowFn, + class CatchFn, + FOLLY_REQUIRES(IsCatchAll::value)> + auto operator()(ThrowFn&& th, CatchFn& ca) const { + return [ th = std::forward(th), &ca, handled_ = handled_ ] { + try { + th(); + } catch (...) { + // If we got here because a catch function threw, rethrow. + if (*handled_) { + throw; + } + *handled_ = true; + ca(); + } + }; + } +}; + +// When all the handlers expect types derived from std::exception, we can +// sometimes invoke the handlers without throwing any exceptions. +template +struct exception_wrapper::HandleStdExceptReduce { + using StdEx = AddConstIf; + + template < + class ThrowFn, + class CatchFn, + FOLLY_REQUIRES(!IsCatchAll::value)> + auto operator()(ThrowFn&& th, CatchFn& ca) const { + using Ex = _t>; + return [ th = std::forward(th), &ca ](auto&& continuation) + -> StdEx* { + if (auto e = const_cast(th(continuation))) { + if (auto e2 = dynamic_cast<_t>>(e)) { + ca(*e2); + } else { + return e; + } + } + return nullptr; + }; + } + + template < + class ThrowFn, + class CatchFn, + FOLLY_REQUIRES(IsCatchAll::value)> + auto operator()(ThrowFn&& th, CatchFn& ca) const { + return [ th = std::forward(th), &ca ](auto&&) -> StdEx* { + // The following continuation causes ca() to execute if *this contains + // an exception /not/ derived from std::exception. + auto continuation = [&ca](StdEx* e) { + return e != nullptr ? e : ((void)ca(), nullptr); + }; + if (th(continuation) != nullptr) { + ca(); + } + return nullptr; + }; + } +}; + +// Called when some types in the catch clauses are not derived from +// std::exception. +template +inline void exception_wrapper::handle_( + std::false_type, This& this_, CatchFns&... fns) { + bool handled = false; + auto impl = exception_wrapper_detail::fold( + HandleReduce::value>{&handled}, + [&] { this_.throwException(); }, + fns...); + impl(); +} + +// Called when all types in the catch clauses are either derived from +// std::exception or a catch-all clause. +template +inline void exception_wrapper::handle_( + std::true_type, This& this_, CatchFns&... fns) { + using StdEx = exception_wrapper_detail:: + AddConstIf::value, std::exception>; + auto impl = exception_wrapper_detail::fold( + HandleStdExceptReduce::value>{}, + [&](auto&& continuation) { + return continuation( + const_cast(this_.vptr_->get_exception_(&this_))); + }, + fns...); + // This continuation gets evaluated if CatchFns... does not include a + // catch-all handler. It is a no-op. + auto continuation = [](StdEx* ex) { return ex; }; + if (StdEx* e = impl(continuation)) { + throw *e; // Not handled. Throw. + } +} + +namespace exception_wrapper_detail { +template +struct catch_fn { + Fn fn_; + auto operator()(Ex& ex) { + return fn_(ex); + } +}; + +template +inline catch_fn catch_(Ex*, Fn fn) { + return {std::move(fn)}; +} +template +inline Fn catch_(void const*, Fn fn) { + return fn; +} +} // namespace exception_wrapper_detail + +template +inline bool exception_wrapper::with_exception_(This& this_, Fn fn_) { + if (!this_) { + return false; + } + bool handled = true; + auto fn = exception_wrapper_detail::catch_( + static_cast(nullptr), std::move(fn_)); + auto&& all = [&](...) { handled = false; }; + handle_(IsStdException>{}, this_, fn, all); + return handled; +} + +template +inline bool exception_wrapper::with_exception(Fn fn) { + return with_exception_(*this, std::move(fn)); +} +template +inline bool exception_wrapper::with_exception(Fn fn) const { + return with_exception_(*this, std::move(fn)); +} + +template +inline void exception_wrapper::handle(CatchFns... fns) { + using AllStdEx = + exception_wrapper_detail::AllOf...>; + if (!*this) { + onNoExceptionError(); + } + this->handle_(AllStdEx{}, *this, fns...); +} +template +inline void exception_wrapper::handle(CatchFns... fns) const { + using AllStdEx = + exception_wrapper_detail::AllOf...>; + if (!*this) { + onNoExceptionError(); + } + this->handle_(AllStdEx{}, *this, fns...); +} + +} // namespace folly diff --git a/folly/ExceptionWrapper.cpp b/folly/ExceptionWrapper.cpp index c6ad1667..c1440a57 100644 --- a/folly/ExceptionWrapper.cpp +++ b/folly/ExceptionWrapper.cpp @@ -15,50 +15,53 @@ */ #include -#include #include +#include + 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(); } diff --git a/folly/ExceptionWrapper.h b/folly/ExceptionWrapper.h index bd5bc119..fa91d458 100644 --- a/folly/ExceptionWrapper.h +++ b/folly/ExceptionWrapper.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Facebook, Inc. + * Copyright 2017-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,416 +13,681 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* + * Author: Eric Niebler + */ #pragma once +#include +#include #include +#include #include -#include -#include +#include #include +#include #include +#include +#include +#include #include #include #include +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wpotentially-evaluated-expression" +// GCC gets confused about lambda scopes and issues shadow-local warnings for +// parameters in totally different functions. +#pragma GCC diagnostic ignored "-Wshadow-local" +#pragma GCC diagnostic ignored "-Wshadow-compatible-local" +#endif + +#define FOLLY_EXCEPTION_WRAPPER_H_INCLUDED + namespace folly { -/* - * Throwing exceptions can be a convenient way to handle errors. Storing - * exceptions in an exception_ptr makes it easy to handle exceptions in a - * different thread or at a later time. exception_ptr can also be used in a very - * generic result/exception wrapper. - * - * However, there are some issues with throwing exceptions and - * std::exception_ptr. These issues revolve around throw being expensive, - * particularly in a multithreaded environment (see - * ExceptionWrapperBenchmark.cpp). - * - * Imagine we have a library that has an API which returns a result/exception - * wrapper. Let's consider some approaches for implementing this wrapper. - * First, we could store a std::exception. This approach loses the derived - * exception type, which can make exception handling more difficult for users - * that prefer rethrowing the exception. We could use a folly::dynamic for every - * possible type of exception. This is not very flexible - adding new types of - * exceptions requires a change to the result/exception wrapper. We could use an - * exception_ptr. However, constructing an exception_ptr as well as accessing - * the error requires a call to throw. That means that there will be two calls - * to throw in order to process the exception. For performance sensitive - * applications, this may be unacceptable. - * - * exception_wrapper is designed to handle exception management for both - * convenience and high performance use cases. make_exception_wrapper is - * templated on derived type, allowing us to rethrow the exception properly for - * users that prefer convenience. These explicitly named exception types can - * therefore be handled without any peformance penalty. exception_wrapper is - * also flexible enough to accept any type. If a caught exception is not of an - * explicitly named type, then std::exception_ptr is used to preserve the - * exception state. For performance sensitive applications, the accessor methods - * can test or extract a pointer to a specific exception type with very little - * overhead. - * - * \par Example usage: - * \par - * \code - * exception_wrapper globalExceptionWrapper; - * - * // Thread1 - * void doSomethingCrazy() { - * int rc = doSomethingCrazyWithLameReturnCodes(); - * if (rc == NAILED_IT) { - * globalExceptionWrapper = exception_wrapper(); - * } else if (rc == FACE_PLANT) { - * globalExceptionWrapper = make_exception_wrapper(); - * } else if (rc == FAIL_WHALE) { - * globalExceptionWrapper = make_exception_wrapper(); - * } - * } - * - * // Thread2: Exceptions are ok! - * void processResult() { - * try { - * globalExceptionWrapper.throwException(); - * } catch (const FacePlantException& e) { - * LOG(ERROR) << "FACEPLANT!"; - * } catch (const FailWhaleException& e) { - * LOG(ERROR) << "FAILWHALE!"; - * } - * } - * - * // Thread2: Exceptions are bad! - * void processResult() { - * globalExceptionWrapper.with_exception( - * [&](FacePlantException& faceplant) { - * LOG(ERROR) << "FACEPLANT"; - * }) || - * globalExceptionWrapper.with_exception( - * [&](FailWhaleException& failwhale) { - * LOG(ERROR) << "FAILWHALE!"; - * }) || - * LOG(FATAL) << "Unrecognized exception"; - * } - * \endcode - * - */ -class exception_wrapper { - private: - template - using is_exception_ = std::is_base_of; +#define FOLLY_REQUIRES_DEF(...) \ + _t(__VA_ARGS__), long>> - public: - exception_wrapper() = default; +#define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__ - template < - typename Ex, - typename DEx = _t>, - typename = _t::value>>, - typename = decltype(DEx(std::forward(std::declval())))> - /* implicit */ exception_wrapper(Ex&& exn) { - assign_sptr(std::forward(exn)); - } +namespace exception_wrapper_detail { - // 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 - explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) { - assign_eptr(eptr, exn); - } +template