blob: 98ff9cb513de5f0efd73c4c44ac9912f01434947 [file] [log] [blame]
/*
* 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