#include <cassert>
#include <exception>
#include <memory>
+#include <folly/String.h>
#include <folly/detail/ExceptionWrapper.h>
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:
*
* // Thread2: Exceptions are bad!
* void processResult() {
* auto ep = globalExceptionWrapper.get();
- * if (ep) {
- * auto faceplant = dynamic_cast<FacePlantException*>(ep);
- * if (faceplant) {
+ * if (!ep.with_exception<FacePlantException>([&](
+ * FacePlantException& faceplant) {
* LOG(ERROR) << "FACEPLANT";
- * } else {
- * auto failwhale = dynamic_cast<FailWhaleException*>(ep);
- * if (failwhale) {
+ * })) {
+ * ep.with_exception<FailWhaleException>([&](
+ * FailWhaleException& failwhale) {
* LOG(ERROR) << "FAILWHALE!";
- * }
- * }
+ * });
* }
* }
*
void throwException() const {
if (throwfn_) {
throwfn_(item_.get());
+ } else if (eptr_) {
+ std::rethrow_exception(eptr_);
}
}
- std::exception* get() { return item_.get(); }
- const std::exception* get() const { return item_.get(); }
+ explicit operator bool() const {
+ return item_ || eptr_;
+ }
- std::exception* operator->() { return get(); }
- const std::exception* operator->() const { return get(); }
+ // 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(); }
- std::exception& operator*() { assert(get()); return *get(); }
- const std::exception& operator*() const { assert(get()); return *get(); }
+ fbstring what() const {
+ if (item_) {
+ return exceptionStr(*item_.get());
+ } else if (eptr_) {
+ return estr_;
+ } else {
+ return fbstring();
+ }
+ }
- explicit operator bool() const { return get(); }
+ template <class Ex>
+ bool is_compatible_with() const {
+ if (item_) {
+ return dynamic_cast<const Ex*>(getCopied());
+ } else if (eptr_) {
+ try {
+ std::rethrow_exception(eptr_);
+ } catch (std::exception& e) {
+ return dynamic_cast<const Ex*>(&e);
+ } catch (...) {
+ // fall through
+ }
+ }
+ return false;
+ }
+
+ template <class Ex, class F>
+ bool with_exception(F f) {
+ if (item_) {
+ if (auto ex = dynamic_cast<Ex*>(getCopied())) {
+ f(*ex);
+ return true;
+ }
+ } else if (eptr_) {
+ try {
+ std::rethrow_exception(eptr_);
+ } catch (std::exception& e) {
+ if (auto ex = dynamic_cast<Ex*>(&e)) {
+ f(*ex);
+ return true;
+ }
+ } catch (...) {
+ // fall through
+ }
+ }
+ return false;
+ }
+
+ template <class Ex, class F>
+ bool with_exception(F f) const {
+ return with_exception<const Ex>(f);
+ }
std::exception_ptr getExceptionPtr() const {
+ if (eptr_) {
+ return eptr_;
+ }
+
try {
throwException();
} catch (...) {
}
protected:
+ // 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*);
+ // Fallback case: store the library wrapper, which is less efficient
+ // but gets the job done. Also store the the what() string, so we
+ // can at least get it back out without having to rethrow.
+ std::exception_ptr eptr_;
+ std::string estr_;
template <class T, class... Args>
friend exception_wrapper make_exception_wrapper(Args&&... args);
try_and_catch() : Base() {}
+ template <typename Ex>
+ typename std::enable_if<std::is_base_of<std::exception, Ex>::value>::type
+ assign_eptr(Ex& e) {
+ this->eptr_ = std::current_exception();
+ // The cast is needed so we get the desired overload of exceptionStr()
+ this->estr_ = exceptionStr(static_cast<std::exception&>(e)).toStdString();
+ }
+
+ template <typename Ex>
+ typename std::enable_if<!std::is_base_of<std::exception, Ex>::value>::type
+ assign_eptr(Ex& e) {
+ this->eptr_ = std::current_exception();
+ this->estr_ = exceptionStr(e).toStdString();
+ }
+
+ 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>
+ typename std::enable_if<!optimize<Ex>::value>::type
+ assign_exception(Ex& e) {
+ assign_eptr(e);
+ }
+
+ template <typename Ex>
+ typename std::enable_if<optimize<Ex>::value>::type
+ assign_exception(Ex& e) {
+ 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 (const LastException& e) {
- this->item_ = std::make_shared<LastException>(e);
- this->throwfn_ = folly::detail::Thrower<LastException>::doThrow;
+ } catch (LastException& e) {
+ if (typeid(e) == typeid(LastException&)) {
+ assign_exception(e);
+ } else {
+ assign_eptr(e);
+ }
}
}
};
#include <gtest/gtest.h>
#include <stdexcept>
#include <folly/ExceptionWrapper.h>
+#include <folly/Conv.h>
using namespace folly;
[=]() {
throw std::runtime_error(expected);
});
- EXPECT_TRUE(ew.get());
- EXPECT_EQ(ew.get()->what(), expected);
- auto rep = dynamic_cast<std::runtime_error*>(ew.get());
+ EXPECT_TRUE(bool(ew));
+ EXPECT_TRUE(ew.getCopied());
+ EXPECT_EQ(ew.what(), "std::runtime_error: payload");
+ auto rep = ew.is_compatible_with<std::runtime_error>();
EXPECT_TRUE(rep);
// Changing order is like catching in wrong order. Beware of this in your
auto ew2 = try_and_catch<std::runtime_error, std::exception>([=]() {
throw std::runtime_error(expected);
});
- EXPECT_TRUE(ew2.get());
+ EXPECT_TRUE(bool(ew2));
// We are catching a std::exception, not std::runtime_error.
- EXPECT_NE(ew2.get()->what(), expected);
- rep = dynamic_cast<std::runtime_error*>(ew2.get());
- EXPECT_FALSE(rep);
+ EXPECT_FALSE(ew2.getCopied());
+ // But, we can still get the actual type if we want it.
+ rep = ew2.is_compatible_with<std::runtime_error>();
+ EXPECT_TRUE(rep);
// Catches even if not rightmost.
auto ew3 = try_and_catch<std::exception, std::runtime_error>([]() {
throw std::exception();
});
- EXPECT_TRUE(ew3.get());
- EXPECT_NE(ew3.get()->what(), expected);
- rep = dynamic_cast<std::runtime_error*>(ew3.get());
+ EXPECT_TRUE(bool(ew3));
+ EXPECT_NE(ew3.what(), expected);
+ rep = ew3.is_compatible_with<std::runtime_error>();
EXPECT_FALSE(rep);
// If does not catch, throws.
}),
std::exception);
}
+
+class AbstractIntException : public std::exception {
+public:
+ virtual int getInt() const = 0;
+};
+
+class IntException : public AbstractIntException {
+public:
+ explicit IntException(int i)
+ : i_(i) {}
+
+ virtual int getInt() const { return i_; }
+ virtual const char* what() const noexcept override {
+ what_ = folly::to<std::string>("int == ", i_);
+ return what_.c_str();
+ }
+
+private:
+ int i_;
+ mutable std::string what_;
+};
+
+TEST(ExceptionWrapper, with_exception_test) {
+ int expected = 23;
+
+ // This works, and doesn't slice.
+ exception_wrapper ew = try_and_catch<std::exception, std::runtime_error>(
+ [=]() {
+ throw IntException(expected);
+ });
+ EXPECT_TRUE(bool(ew));
+ EXPECT_EQ(ew.what(), "IntException: int == 23");
+ ew.with_exception<IntException>([&](const IntException& ie) {
+ EXPECT_EQ(ie.getInt(), expected);
+ });
+
+ // I can try_and_catch a non-copyable base class. This will use
+ // std::exception_ptr internally.
+ exception_wrapper ew2 = try_and_catch<AbstractIntException>(
+ [=]() {
+ throw IntException(expected);
+ });
+ EXPECT_TRUE(bool(ew2));
+ EXPECT_EQ(ew2.what(), "IntException: int == 23");
+ ew2.with_exception<AbstractIntException>([&](AbstractIntException& ie) {
+ EXPECT_EQ(ie.getInt(), expected);
+ EXPECT_EQ(typeid(ie), typeid(IntException));
+ });
+}
+
+TEST(ExceptionWrapper, non_std_exception_test) {
+ int expected = 17;
+
+ exception_wrapper ew = try_and_catch<std::exception, int>(
+ [=]() {
+ throw expected;
+ });
+ EXPECT_TRUE(bool(ew));
+ EXPECT_FALSE(ew.is_compatible_with<std::exception>());
+ EXPECT_EQ(ew.what(), "int");
+ // non-std::exception types are supported, but the only way to
+ // access their value is to explicity rethrow and catch it.
+ try {
+ ew.throwException();
+ } catch (int& i) {
+ EXPECT_EQ(i, expected);
+ }
+}