/* * Copyright 2016 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. */ #pragma once #include #include #include #include #include #include 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(); * } 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"; * } * */ class exception_wrapper { protected: template struct optimize; public: exception_wrapper() = default; // Implicitly construct an exception_wrapper from a qualifying exception. // See the optimize struct for details. template ::type>::value> ::type> /* implicit */ exception_wrapper(Ex&& exn) { typedef typename std::decay::type DEx; item_ = std::make_shared(std::forward(exn)); throwfn_ = folly::detail::Thrower::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 explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) { assign_eptr(eptr, exn); } explicit exception_wrapper(std::exception_ptr eptr) { assign_eptr(eptr); } // If the exception_wrapper does not contain an exception, std::terminate() // is invoked to assure the [[noreturn]] behaviour. [[noreturn]] void throwException() const { if (throwfn_) { throwfn_(item_.get()); } else if (eptr_) { std::rethrow_exception(eptr_); } std::cerr << "Cannot use `throwException` with an empty folly::exception_wrapper" << std::endl; std::terminate(); } 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 bool is_compatible_with() const { if (item_) { return dynamic_cast(item_.get()); } else if (eptr_) { try { std::rethrow_exception(eptr_); } catch (typename std::decay::type&) { return true; } catch (...) { // fall through } } return false; } template bool with_exception(F&& f) { using arg_type = typename functor_traits::arg_type_decayed; return with_exception(std::forward(f)); } template bool with_exception(F&& f) const { using arg_type = typename functor_traits::arg_type_decayed; return with_exception(std::forward(f)); } // 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 typename std::enable_if< std::is_base_of::type>::value, bool>::type with_exception(F f) { return with_exception1::type>(f, this); } // Const overload template typename std::enable_if< std::is_base_of::type>::value, bool>::type with_exception(F f) const { return with_exception1::type>(f, this); } // Overload for non-exceptions. Always rethrows. template typename std::enable_if< !std::is_base_of::type>::value, bool>::type with_exception(F f) const { try { if (*this) { throwException(); } } catch (typename std::decay::type& e) { f(e); return true; } catch (...) { // fall through } return false; } std::exception_ptr getExceptionPtr() const { if (eptr_) { return eptr_; } try { if (*this) { throwException(); } } catch (...) { return std::current_exception(); } return std::exception_ptr(); } protected: template struct optimize { static const bool value = std::is_base_of::value && std::is_copy_assignable::value && !std::is_abstract::value; }; template 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 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 friend exception_wrapper make_exception_wrapper(Args&&... args); private: template struct functor_traits { template struct impl; template struct impl { using arg_type = A; }; template struct impl { using arg_type = A; }; using functor_decayed = typename std::decay::type; using functor_op = decltype(&functor_decayed::operator()); using arg_type = typename impl::arg_type; using arg_type_decayed = typename std::decay::type; }; // 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 static bool with_exception1(F f, T* that) { if (that->item_) { if (auto ex = dynamic_cast(that->item_.get())) { f(*ex); return true; } } else if (that->eptr_) { try { std::rethrow_exception(that->eptr_); } catch (Ex& e) { f(e); return true; } catch (...) { // fall through } } return false; } }; template exception_wrapper make_exception_wrapper(Args&&... args) { exception_wrapper ew; ew.item_ = std::make_shared(std::forward(args)...); ew.throwfn_ = folly::detail::Thrower::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([=]() { * 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([=]() { * if (badThingHappens()) { * throw std::exception(); * } * }); * * // This will not catch the exception and it will be thrown. * auto ew = folly::try_and_catch([=]() { * if (badThingHappens()) { * throw std::exception(); * } * }); */ template class try_and_catch; template class try_and_catch : public try_and_catch { public: template explicit try_and_catch(F&& fn) : Base() { call_fn(fn); } protected: typedef try_and_catch Base; try_and_catch() : Base() {} template typename std::enable_if::value>::type assign_exception(Ex& e, std::exception_ptr eptr) { exception_wrapper::assign_eptr(eptr, e); } template typename std::enable_if::value>::type assign_exception(Ex& e, std::exception_ptr /*eptr*/) { this->item_ = std::make_shared(e); this->throwfn_ = folly::detail::Thrower::doThrow; } template 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 void call_fn(F&& fn) { fn(); } }; }