X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2FExceptionWrapper.h;h=ca57282c524ea0f293216bf8bc9acb416ec25696;hb=3c4a013ff85c5ff329c7270094f31881629b3581;hp=23a9ac7a68dffe5eb0b4405f46ce9064fbd1ba5f;hpb=c31b528b862af14917131eeb9631dd1f5d4d4477;p=folly.git diff --git a/folly/ExceptionWrapper.h b/folly/ExceptionWrapper.h index 23a9ac7a..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,43 +87,194 @@ 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_; } } - std::exception* get() { return item_.get(); } - const std::exception* get() const { return item_.get(); } + 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(); + } + } - std::exception* operator->() { return get(); } - const std::exception* operator->() const { return get(); } + 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& operator*() { assert(get()); return *get(); } - const std::exception& operator*() const { assert(get()); return *get(); } + template + bool with_exception(F&& f) { + using arg_type = typename functor_traits::arg_type_decayed; + return with_exception(std::forward(f)); + } - explicit operator bool() 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)); + } + + // 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 { + 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 (...) { @@ -130,12 +283,79 @@ class exception_wrapper { return std::exception_ptr(); } - protected: +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*); + 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 @@ -146,6 +366,11 @@ 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 @@ -203,13 +428,29 @@ class try_and_catch : 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 (const LastException& e) { - this->item_ = std::make_shared(e); - this->throwfn_ = folly::detail::Thrower::doThrow; + } catch (LastException& e) { + if (typeid(e) == typeid(LastException&)) { + assign_exception(e, std::current_exception()); + } else { + exception_wrapper::assign_eptr(std::current_exception(), e); + } } } }; @@ -217,7 +458,7 @@ class try_and_catch : template<> class try_and_catch<> : public exception_wrapper { public: - try_and_catch() {} + try_and_catch() = default; protected: template @@ -226,4 +467,3 @@ class try_and_catch<> : public exception_wrapper { } }; } -#endif