2 * Copyright 2014 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #ifndef FOLLY_EXCEPTIONWRAPPER_H
18 #define FOLLY_EXCEPTIONWRAPPER_H
23 #include <folly/String.h>
24 #include <folly/detail/ExceptionWrapper.h>
29 * Throwing exceptions can be a convenient way to handle errors. Storing
30 * exceptions in an exception_ptr makes it easy to handle exceptions in a
31 * different thread or at a later time. exception_ptr can also be used in a very
32 * generic result/exception wrapper.
34 * However, there are some issues with throwing exceptions and
35 * std::exception_ptr. These issues revolve around throw being expensive,
36 * particularly in a multithreaded environment (see
37 * ExceptionWrapperBenchmark.cpp).
39 * Imagine we have a library that has an API which returns a result/exception
40 * wrapper. Let's consider some approaches for implementing this wrapper.
41 * First, we could store a std::exception. This approach loses the derived
42 * exception type, which can make exception handling more difficult for users
43 * that prefer rethrowing the exception. We could use a folly::dynamic for every
44 * possible type of exception. This is not very flexible - adding new types of
45 * exceptions requires a change to the result/exception wrapper. We could use an
46 * exception_ptr. However, constructing an exception_ptr as well as accessing
47 * the error requires a call to throw. That means that there will be two calls
48 * to throw in order to process the exception. For performance sensitive
49 * applications, this may be unacceptable.
51 * exception_wrapper is designed to handle exception management for both
52 * convenience and high performance use cases. make_exception_wrapper is
53 * templated on derived type, allowing us to rethrow the exception properly for
54 * users that prefer convenience. These explicitly named exception types can
55 * therefore be handled without any peformance penalty. exception_wrapper is
56 * also flexible enough to accept any type. If a caught exception is not of an
57 * explicitly named type, then std::exception_ptr is used to preserve the
58 * exception state. For performance sensitive applications, the accessor methods
59 * can test or extract a pointer to a specific exception type with very little
64 * exception_wrapper globalExceptionWrapper;
67 * void doSomethingCrazy() {
68 * int rc = doSomethingCrazyWithLameReturnCodes();
69 * if (rc == NAILED_IT) {
70 * globalExceptionWrapper = exception_wrapper();
71 * } else if (rc == FACE_PLANT) {
72 * globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
73 * } else if (rc == FAIL_WHALE) {
74 * globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
78 * // Thread2: Exceptions are ok!
79 * void processResult() {
81 * globalExceptionWrapper.throwException();
82 * } catch (const FacePlantException& e) {
83 * LOG(ERROR) << "FACEPLANT!";
84 * } catch (const FailWhaleException& e) {
85 * LOG(ERROR) << "FAILWHALE!";
89 * // Thread2: Exceptions are bad!
90 * void processResult() {
91 * auto ep = globalExceptionWrapper.get();
92 * if (!ep.with_exception<FacePlantException>([&](
93 * FacePlantException& faceplant) {
94 * LOG(ERROR) << "FACEPLANT";
96 * ep.with_exception<FailWhaleException>([&](
97 * FailWhaleException& failwhale) {
98 * LOG(ERROR) << "FAILWHALE!";
104 class exception_wrapper {
106 exception_wrapper() : throwfn_(nullptr) { }
108 void throwException() const {
110 throwfn_(item_.get());
112 std::rethrow_exception(eptr_);
116 explicit operator bool() const {
117 return item_ || eptr_;
120 // This implementation is similar to std::exception_ptr's implementation
121 // where two exception_wrappers are equal when the address in the underlying
122 // reference field both point to the same exception object. The reference
123 // field remains the same when the exception_wrapper is copied or when
124 // the exception_wrapper is "rethrown".
125 bool operator==(const exception_wrapper& a) const {
127 return a.item_ && item_.get() == a.item_.get();
129 return eptr_ == a.eptr_;
133 bool operator!=(const exception_wrapper& a) const {
134 return !(*this == a);
137 // This will return a non-nullptr only if the exception is held as a
138 // copy. It is the only interface which will distinguish between an
139 // exception held this way, and by exception_ptr. You probably
140 // shouldn't use it at all.
141 std::exception* getCopied() { return item_.get(); }
142 const std::exception* getCopied() const { return item_.get(); }
144 fbstring what() const {
146 return exceptionStr(*item_.get());
155 bool is_compatible_with() const {
157 return dynamic_cast<const Ex*>(getCopied());
160 std::rethrow_exception(eptr_);
161 } catch (std::exception& e) {
162 return dynamic_cast<const Ex*>(&e);
170 template <class Ex, class F>
171 bool with_exception(F f) {
173 if (auto ex = dynamic_cast<Ex*>(getCopied())) {
179 std::rethrow_exception(eptr_);
180 } catch (std::exception& e) {
181 if (auto ex = dynamic_cast<Ex*>(&e)) {
192 template <class Ex, class F>
193 bool with_exception(F f) const {
194 return with_exception<const Ex>(f);
197 std::exception_ptr getExceptionPtr() const {
205 return std::current_exception();
207 return std::exception_ptr();
211 // Optimized case: if we know what type the exception is, we can
212 // store a copy of the concrete type, and a helper function so we
214 std::shared_ptr<std::exception> item_;
215 void (*throwfn_)(std::exception*);
216 // Fallback case: store the library wrapper, which is less efficient
217 // but gets the job done. Also store the the what() string, so we
218 // can at least get it back out without having to rethrow.
219 std::exception_ptr eptr_;
222 template <class T, class... Args>
223 friend exception_wrapper make_exception_wrapper(Args&&... args);
226 template <class T, class... Args>
227 exception_wrapper make_exception_wrapper(Args&&... args) {
228 exception_wrapper ew;
229 ew.item_ = std::make_shared<T>(std::forward<Args>(args)...);
230 ew.throwfn_ = folly::detail::Thrower<T>::doThrow;
235 * try_and_catch is a simple replacement for try {} catch(){} that allows you to
236 * specify which derived exceptions you would like to catch and store in an
239 * Because we cannot build an equivalent of std::current_exception(), we need
240 * to catch every derived exception that we are interested in catching.
242 * Exceptions should be listed in the reverse order that you would write your
243 * catch statements (that is, std::exception& should be first).
245 * NOTE: Although implemented as a derived class (for syntactic delight), don't
246 * be confused - you should not pass around try_and_catch objects!
250 * // This catches my runtime_error and if I call throwException() on ew, it
251 * // will throw a runtime_error
252 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
253 * if (badThingHappens()) {
254 * throw std::runtime_error("ZOMG!");
258 * // This will catch the exception and if I call throwException() on ew, it
259 * // will throw a std::exception
260 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
261 * if (badThingHappens()) {
262 * throw std::exception();
266 * // This will not catch the exception and it will be thrown.
267 * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
268 * if (badThingHappens()) {
269 * throw std::exception();
274 template <typename... Exceptions>
277 template <typename LastException, typename... Exceptions>
278 class try_and_catch<LastException, Exceptions...> :
279 public try_and_catch<Exceptions...> {
281 template <typename F>
282 explicit try_and_catch(F&& fn) : Base() {
287 typedef try_and_catch<Exceptions...> Base;
289 try_and_catch() : Base() {}
291 template <typename Ex>
292 typename std::enable_if<std::is_base_of<std::exception, Ex>::value>::type
294 this->eptr_ = std::current_exception();
295 this->estr_ = exceptionStr(e).toStdString();
298 template <typename Ex>
299 typename std::enable_if<!std::is_base_of<std::exception, Ex>::value>::type
301 this->eptr_ = std::current_exception();
302 this->estr_ = exceptionStr(e).toStdString();
305 template <typename Ex>
307 static const bool value =
308 std::is_base_of<std::exception, Ex>::value &&
309 std::is_copy_assignable<Ex>::value &&
310 !std::is_abstract<Ex>::value;
313 template <typename Ex>
314 typename std::enable_if<!optimize<Ex>::value>::type
315 assign_exception(Ex& e) {
319 template <typename Ex>
320 typename std::enable_if<optimize<Ex>::value>::type
321 assign_exception(Ex& e) {
322 this->item_ = std::make_shared<Ex>(e);
323 this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
326 template <typename F>
327 void call_fn(F&& fn) {
329 Base::call_fn(std::move(fn));
330 } catch (LastException& e) {
331 if (typeid(e) == typeid(LastException&)) {
341 class try_and_catch<> : public exception_wrapper {
346 template <typename F>
347 void call_fn(F&& fn) {