X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;ds=sidebyside;f=folly%2FExceptionWrapper.h;h=ca57282c524ea0f293216bf8bc9acb416ec25696;hb=6c76acfc86177dd2570bf80411baa47dd2b74c72;hp=a18ee1f01b702caacccb6ab97b0d59f98e0d4166;hpb=2833ce1b88dca29d2b2a172f31cde28aaa14a0b8;p=folly.git diff --git a/folly/ExceptionWrapper.h b/folly/ExceptionWrapper.h index a18ee1f0..ca57282c 100644 --- a/folly/ExceptionWrapper.h +++ b/folly/ExceptionWrapper.h @@ -1,5 +1,5 @@ /* - * Copyright 2014 Facebook, Inc. + * 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. @@ -14,13 +14,13 @@ * limitations under the License. */ -#ifndef FOLLY_EXCEPTIONWRAPPER_H -#define FOLLY_EXCEPTIONWRAPPER_H +#pragma once #include #include #include -#include "folly/detail/ExceptionWrapper.h" +#include +#include namespace folly { @@ -50,11 +50,13 @@ namespace folly { * 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. exception_wrapper is flexible enough to accept - * any std::exception. For performance sensitive applications, exception_wrapper - * exposes a get() function. These users can use dynamic_cast to retrieve - * desired derived types (hence the decision to limit usage to just - * std::exception instead of void*). + * 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: * @@ -85,56 +87,275 @@ namespace folly { * * // Thread2: Exceptions are bad! * void processResult() { - * auto ep = globalExceptionWrapper.get(); - * if (ep) { - * auto faceplant = dynamic_cast(ep); - * if (faceplant) { - * LOG(ERROR) << "FACEPLANT"; - * } else { - * auto failwhale = dynamic_cast(ep); - * if (failwhale) { + * 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() : throwfn_(nullptr) { } + 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); + } 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 + 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; } - std::exception* get() { return item_.get(); } - const std::exception* get() const { return item_.get(); } + template + bool with_exception(F&& f) { + using arg_type = typename functor_traits::arg_type_decayed; + return with_exception(std::forward(f)); + } - std::exception* operator->() { return get(); } - const std::exception* operator->() const { return get(); } + template + bool with_exception(F&& f) const { + using arg_type = typename functor_traits::arg_type_decayed; + return with_exception(std::forward(f)); + } - std::exception& operator*() { assert(get()); return *get(); } - const std::exception& operator*() const { assert(get()); return *get(); } + // 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); + } - explicit operator bool() const { return get(); } + // 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 { + 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 { 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(); } - private: + 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*); + 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 @@ -145,5 +366,104 @@ exception_wrapper make_exception_wrapper(Args&&... args) { 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(); + } +}; } -#endif