| /* |
| * Copyright 2015 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. |
| */ |
| |
| #ifndef FOLLY_EXCEPTIONWRAPPER_H |
| #define FOLLY_EXCEPTIONWRAPPER_H |
| |
| #include <cassert> |
| #include <exception> |
| #include <memory> |
| #include <folly/String.h> |
| #include <folly/detail/ExceptionWrapper.h> |
| |
| 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. |
| * |
| * Example usage: |
| * |
| * 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<FacePlantException>(); |
| * } else if (rc == FAIL_WHALE) { |
| * globalExceptionWrapper = make_exception_wrapper<FailWhaleException>(); |
| * } |
| * } |
| * |
| * // 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() { |
| * auto ep = globalExceptionWrapper.get(); |
| * if (!ep.with_exception<FacePlantException>([&]( |
| * FacePlantException& faceplant) { |
| * LOG(ERROR) << "FACEPLANT"; |
| * })) { |
| * ep.with_exception<FailWhaleException>([&]( |
| * FailWhaleException& failwhale) { |
| * LOG(ERROR) << "FAILWHALE!"; |
| * }); |
| * } |
| * } |
| * |
| */ |
| class exception_wrapper { |
| protected: |
| template <typename Ex> |
| struct optimize; |
| |
| public: |
| 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<typename std::decay<Ex>::type>::value> |
| ::type> |
| /* implicit */ exception_wrapper(Ex&& exn) { |
| typedef typename std::decay<Ex>::type DEx; |
| item_ = std::make_shared<DEx>(std::forward<Ex>(exn)); |
| throwfn_ = folly::detail::Thrower<DEx>::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 { |
| if (throwfn_) { |
| throwfn_(item_.get()); |
| } else if (eptr_) { |
| std::rethrow_exception(eptr_); |
| } |
| } |
| |
| explicit operator bool() const { |
| return item_ || eptr_; |
| } |
| |
| // This implementation is similar to std::exception_ptr's implementation |
| // where two exception_wrappers are equal when the address in the underlying |
| // reference field both point to the same exception object. The reference |
| // field remains the same when the exception_wrapper is copied or when |
| // the exception_wrapper is "rethrown". |
| bool operator==(const exception_wrapper& a) const { |
| if (item_) { |
| return a.item_ && item_.get() == a.item_.get(); |
| } else { |
| return eptr_ == a.eptr_; |
| } |
| } |
| |
| bool operator!=(const exception_wrapper& a) const { |
| return !(*this == a); |
| } |
| |
| // This will return a non-nullptr only if the exception is held as a |
| // copy. It is the only interface which will distinguish between an |
| // exception held this way, and by exception_ptr. You probably |
| // shouldn't use it at all. |
| std::exception* getCopied() { return item_.get(); } |
| const std::exception* getCopied() const { return item_.get(); } |
| |
| fbstring what() const { |
| if (item_) { |
| return exceptionStr(*item_); |
| } else if (eptr_) { |
| return estr_; |
| } else { |
| return fbstring(); |
| } |
| } |
| |
| fbstring class_name() const { |
| if (item_) { |
| auto& i = *item_; |
| return demangle(typeid(i)); |
| } else if (eptr_) { |
| return ename_; |
| } else { |
| return fbstring(); |
| } |
| } |
| |
| template <class Ex> |
| bool is_compatible_with() const { |
| if (item_) { |
| return dynamic_cast<const Ex*>(item_.get()); |
| } else if (eptr_) { |
| try { |
| std::rethrow_exception(eptr_); |
| } catch (std::exception& e) { |
| return dynamic_cast<const Ex*>(&e); |
| } catch (...) { |
| // fall through |
| } |
| } |
| 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> |
| 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> |
| 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 { |
| if (eptr_) { |
| return eptr_; |
| } |
| |
| try { |
| throwException(); |
| } catch (...) { |
| return std::current_exception(); |
| } |
| return std::exception_ptr(); |
| } |
| |
| 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*){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 |
| // having to rethrow. |
| std::exception_ptr eptr_; |
| std::string estr_; |
| std::string ename_; |
| |
| template <class T, class... Args> |
| friend exception_wrapper make_exception_wrapper(Args&&... args); |
| |
| private: |
| // What makes this useful is that T can be exception_wrapper* or |
| // const exception_wrapper*, and the compiler will use the |
| // instantiation which works with F. |
| template <class Ex, class F, class T> |
| static bool with_exception1(F f, T* that) { |
| if (that->item_) { |
| if (auto ex = dynamic_cast<Ex*>(that->item_.get())) { |
| f(*ex); |
| return true; |
| } |
| } else if (that->eptr_) { |
| try { |
| std::rethrow_exception(that->eptr_); |
| } catch (std::exception& e) { |
| if (auto ex = dynamic_cast<Ex*>(&e)) { |
| f(*ex); |
| return true; |
| } |
| } catch (...) { |
| // fall through |
| } |
| } |
| return false; |
| } |
| }; |
| |
| template <class T, class... Args> |
| exception_wrapper make_exception_wrapper(Args&&... args) { |
| exception_wrapper ew; |
| ew.item_ = std::make_shared<T>(std::forward<Args>(args)...); |
| ew.throwfn_ = folly::detail::Thrower<T>::doThrow; |
| return ew; |
| } |
| |
| // For consistency with exceptionStr() functions in String.h |
| inline fbstring exceptionStr(const exception_wrapper& ew) { |
| return ew.what(); |
| } |
| |
| /* |
| * try_and_catch is a simple replacement for try {} catch(){} that allows you to |
| * specify which derived exceptions you would like to catch and store in an |
| * exception_wrapper. |
| * |
| * Because we cannot build an equivalent of std::current_exception(), we need |
| * to catch every derived exception that we are interested in catching. |
| * |
| * Exceptions should be listed in the reverse order that you would write your |
| * catch statements (that is, std::exception& should be first). |
| * |
| * NOTE: Although implemented as a derived class (for syntactic delight), don't |
| * be confused - you should not pass around try_and_catch objects! |
| * |
| * Example Usage: |
| * |
| * // This catches my runtime_error and if I call throwException() on ew, it |
| * // will throw a runtime_error |
| * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() { |
| * if (badThingHappens()) { |
| * throw std::runtime_error("ZOMG!"); |
| * } |
| * }); |
| * |
| * // This will catch the exception and if I call throwException() on ew, it |
| * // will throw a std::exception |
| * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() { |
| * if (badThingHappens()) { |
| * throw std::exception(); |
| * } |
| * }); |
| * |
| * // This will not catch the exception and it will be thrown. |
| * auto ew = folly::try_and_catch<std::runtime_error>([=]() { |
| * if (badThingHappens()) { |
| * throw std::exception(); |
| * } |
| * }); |
| */ |
| |
| template <typename... Exceptions> |
| class try_and_catch; |
| |
| template <typename LastException, typename... Exceptions> |
| class try_and_catch<LastException, Exceptions...> : |
| public try_and_catch<Exceptions...> { |
| public: |
| template <typename F> |
| explicit try_and_catch(F&& fn) : Base() { |
| call_fn(fn); |
| } |
| |
| protected: |
| typedef try_and_catch<Exceptions...> Base; |
| |
| try_and_catch() : Base() {} |
| |
| template <typename Ex> |
| 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<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; |
| } |
| |
| template <typename F> |
| void call_fn(F&& fn) { |
| try { |
| Base::call_fn(std::move(fn)); |
| } catch (LastException& e) { |
| if (typeid(e) == typeid(LastException&)) { |
| assign_exception(e, std::current_exception()); |
| } else { |
| exception_wrapper::assign_eptr(std::current_exception(), e); |
| } |
| } |
| } |
| }; |
| |
| template<> |
| class try_and_catch<> : public exception_wrapper { |
| public: |
| try_and_catch() = default; |
| |
| protected: |
| template <typename F> |
| void call_fn(F&& fn) { |
| fn(); |
| } |
| }; |
| } |
| #endif |