2 * Copyright 2016 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.
22 #include <folly/ExceptionString.h>
23 #include <folly/detail/ExceptionWrapper.h>
28 * Throwing exceptions can be a convenient way to handle errors. Storing
29 * exceptions in an exception_ptr makes it easy to handle exceptions in a
30 * different thread or at a later time. exception_ptr can also be used in a very
31 * generic result/exception wrapper.
33 * However, there are some issues with throwing exceptions and
34 * std::exception_ptr. These issues revolve around throw being expensive,
35 * particularly in a multithreaded environment (see
36 * ExceptionWrapperBenchmark.cpp).
38 * Imagine we have a library that has an API which returns a result/exception
39 * wrapper. Let's consider some approaches for implementing this wrapper.
40 * First, we could store a std::exception. This approach loses the derived
41 * exception type, which can make exception handling more difficult for users
42 * that prefer rethrowing the exception. We could use a folly::dynamic for every
43 * possible type of exception. This is not very flexible - adding new types of
44 * exceptions requires a change to the result/exception wrapper. We could use an
45 * exception_ptr. However, constructing an exception_ptr as well as accessing
46 * the error requires a call to throw. That means that there will be two calls
47 * to throw in order to process the exception. For performance sensitive
48 * applications, this may be unacceptable.
50 * exception_wrapper is designed to handle exception management for both
51 * convenience and high performance use cases. make_exception_wrapper is
52 * templated on derived type, allowing us to rethrow the exception properly for
53 * users that prefer convenience. These explicitly named exception types can
54 * therefore be handled without any peformance penalty. exception_wrapper is
55 * also flexible enough to accept any type. If a caught exception is not of an
56 * explicitly named type, then std::exception_ptr is used to preserve the
57 * exception state. For performance sensitive applications, the accessor methods
58 * can test or extract a pointer to a specific exception type with very little
63 * exception_wrapper globalExceptionWrapper;
66 * void doSomethingCrazy() {
67 * int rc = doSomethingCrazyWithLameReturnCodes();
68 * if (rc == NAILED_IT) {
69 * globalExceptionWrapper = exception_wrapper();
70 * } else if (rc == FACE_PLANT) {
71 * globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
72 * } else if (rc == FAIL_WHALE) {
73 * globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
77 * // Thread2: Exceptions are ok!
78 * void processResult() {
80 * globalExceptionWrapper.throwException();
81 * } catch (const FacePlantException& e) {
82 * LOG(ERROR) << "FACEPLANT!";
83 * } catch (const FailWhaleException& e) {
84 * LOG(ERROR) << "FAILWHALE!";
88 * // Thread2: Exceptions are bad!
89 * void processResult() {
90 * globalExceptionWrapper.with_exception(
91 * [&](FacePlantException& faceplant) {
92 * LOG(ERROR) << "FACEPLANT";
94 * globalExceptionWrapper.with_exception(
95 * [&](FailWhaleException& failwhale) {
96 * LOG(ERROR) << "FAILWHALE!";
98 * LOG(FATAL) << "Unrecognized exception";
102 class exception_wrapper {
104 template <typename Ex>
108 exception_wrapper() = default;
110 // Implicitly construct an exception_wrapper from a qualifying exception.
111 // See the optimize struct for details.
112 template <typename Ex, typename =
113 typename std::enable_if<optimize<typename std::decay<Ex>::type>::value>
115 /* implicit */ exception_wrapper(Ex&& exn) {
116 typedef typename std::decay<Ex>::type DEx;
117 item_ = std::make_shared<DEx>(std::forward<Ex>(exn));
118 throwfn_ = folly::detail::Thrower<DEx>::doThrow;
121 // The following two constructors are meant to emulate the behavior of
122 // try_and_catch in performance sensitive code as well as to be flexible
123 // enough to wrap exceptions of unknown type. There is an overload that
124 // takes an exception reference so that the wrapper can extract and store
125 // the exception's type and what() when possible.
127 // The canonical use case is to construct an all-catching exception wrapper
128 // with minimal overhead like so:
131 // // some throwing code
132 // } catch (const std::exception& e) {
133 // // won't lose e's type and what()
134 // exception_wrapper ew{std::current_exception(), e};
136 // // everything else
137 // exception_wrapper ew{std::current_exception()};
140 // try_and_catch is cleaner and preferable. Use it unless you're sure you need
141 // something like this instead.
142 template <typename Ex>
143 explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
144 assign_eptr(eptr, exn);
147 explicit exception_wrapper(std::exception_ptr eptr) {
151 void throwException() const {
153 throwfn_(item_.get());
155 std::rethrow_exception(eptr_);
159 explicit operator bool() const {
160 return item_ || eptr_;
163 // This implementation is similar to std::exception_ptr's implementation
164 // where two exception_wrappers are equal when the address in the underlying
165 // reference field both point to the same exception object. The reference
166 // field remains the same when the exception_wrapper is copied or when
167 // the exception_wrapper is "rethrown".
168 bool operator==(const exception_wrapper& a) const {
170 return a.item_ && item_.get() == a.item_.get();
172 return eptr_ == a.eptr_;
176 bool operator!=(const exception_wrapper& a) const {
177 return !(*this == a);
180 // This will return a non-nullptr only if the exception is held as a
181 // copy. It is the only interface which will distinguish between an
182 // exception held this way, and by exception_ptr. You probably
183 // shouldn't use it at all.
184 std::exception* getCopied() { return item_.get(); }
185 const std::exception* getCopied() const { return item_.get(); }
187 fbstring what() const {
189 return exceptionStr(*item_);
197 fbstring class_name() const {
200 return demangle(typeid(i));
209 bool is_compatible_with() const {
211 return dynamic_cast<const Ex*>(item_.get());
214 std::rethrow_exception(eptr_);
215 } catch (typename std::decay<Ex>::type&) {
225 bool with_exception(F&& f) {
226 using arg_type = typename functor_traits<F>::arg_type_decayed;
227 return with_exception<arg_type>(std::forward<F>(f));
231 bool with_exception(F&& f) const {
232 using arg_type = typename functor_traits<F>::arg_type_decayed;
233 return with_exception<const arg_type>(std::forward<F>(f));
236 // If this exception wrapper wraps an exception of type Ex, with_exception
237 // will call f with the wrapped exception as an argument and return true, and
238 // will otherwise return false.
239 template <class Ex, class F>
240 typename std::enable_if<
241 std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
243 with_exception(F f) {
244 return with_exception1<typename std::decay<Ex>::type>(f, this);
248 template <class Ex, class F>
249 typename std::enable_if<
250 std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
252 with_exception(F f) const {
253 return with_exception1<const typename std::decay<Ex>::type>(f, this);
256 // Overload for non-exceptions. Always rethrows.
257 template <class Ex, class F>
258 typename std::enable_if<
259 !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
261 with_exception(F f) const {
264 } catch (typename std::decay<Ex>::type& e) {
273 std::exception_ptr getExceptionPtr() const {
281 return std::current_exception();
283 return std::exception_ptr();
287 template <typename Ex>
289 static const bool value =
290 std::is_base_of<std::exception, Ex>::value &&
291 std::is_copy_assignable<Ex>::value &&
292 !std::is_abstract<Ex>::value;
295 template <typename Ex>
296 void assign_eptr(std::exception_ptr eptr, Ex& e) {
298 this->estr_ = exceptionStr(e).toStdString();
299 this->ename_ = demangle(typeid(e)).toStdString();
302 void assign_eptr(std::exception_ptr eptr) {
306 // Optimized case: if we know what type the exception is, we can
307 // store a copy of the concrete type, and a helper function so we
309 std::shared_ptr<std::exception> item_;
310 void (*throwfn_)(std::exception*){nullptr};
311 // Fallback case: store the library wrapper, which is less efficient
312 // but gets the job done. Also store exceptionPtr() the name of the
313 // exception type, so we can at least get those back out without
314 // having to rethrow.
315 std::exception_ptr eptr_;
319 template <class T, class... Args>
320 friend exception_wrapper make_exception_wrapper(Args&&... args);
323 template <typename F>
324 struct functor_traits {
325 template <typename T>
327 template <typename C, typename R, typename A>
328 struct impl<R(C::*)(A)> { using arg_type = A; };
329 template <typename C, typename R, typename A>
330 struct impl<R(C::*)(A) const> { using arg_type = A; };
331 using functor_decayed = typename std::decay<F>::type;
332 using functor_op = decltype(&functor_decayed::operator());
333 using arg_type = typename impl<functor_op>::arg_type;
334 using arg_type_decayed = typename std::decay<arg_type>::type;
337 // What makes this useful is that T can be exception_wrapper* or
338 // const exception_wrapper*, and the compiler will use the
339 // instantiation which works with F.
340 template <class Ex, class F, class T>
341 static bool with_exception1(F f, T* that) {
343 if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
347 } else if (that->eptr_) {
349 std::rethrow_exception(that->eptr_);
361 template <class T, class... Args>
362 exception_wrapper make_exception_wrapper(Args&&... args) {
363 exception_wrapper ew;
364 ew.item_ = std::make_shared<T>(std::forward<Args>(args)...);
365 ew.throwfn_ = folly::detail::Thrower<T>::doThrow;
369 // For consistency with exceptionStr() functions in String.h
370 inline fbstring exceptionStr(const exception_wrapper& ew) {
375 * try_and_catch is a simple replacement for try {} catch(){} that allows you to
376 * specify which derived exceptions you would like to catch and store in an
379 * Because we cannot build an equivalent of std::current_exception(), we need
380 * to catch every derived exception that we are interested in catching.
382 * Exceptions should be listed in the reverse order that you would write your
383 * catch statements (that is, std::exception& should be first).
385 * NOTE: Although implemented as a derived class (for syntactic delight), don't
386 * be confused - you should not pass around try_and_catch objects!
390 * // This catches my runtime_error and if I call throwException() on ew, it
391 * // will throw a runtime_error
392 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
393 * if (badThingHappens()) {
394 * throw std::runtime_error("ZOMG!");
398 * // This will catch the exception and if I call throwException() on ew, it
399 * // will throw a std::exception
400 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
401 * if (badThingHappens()) {
402 * throw std::exception();
406 * // This will not catch the exception and it will be thrown.
407 * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
408 * if (badThingHappens()) {
409 * throw std::exception();
414 template <typename... Exceptions>
417 template <typename LastException, typename... Exceptions>
418 class try_and_catch<LastException, Exceptions...> :
419 public try_and_catch<Exceptions...> {
421 template <typename F>
422 explicit try_and_catch(F&& fn) : Base() {
427 typedef try_and_catch<Exceptions...> Base;
429 try_and_catch() : Base() {}
431 template <typename Ex>
432 typename std::enable_if<!exception_wrapper::optimize<Ex>::value>::type
433 assign_exception(Ex& e, std::exception_ptr eptr) {
434 exception_wrapper::assign_eptr(eptr, e);
437 template <typename Ex>
438 typename std::enable_if<exception_wrapper::optimize<Ex>::value>::type
439 assign_exception(Ex& e, std::exception_ptr /*eptr*/) {
440 this->item_ = std::make_shared<Ex>(e);
441 this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
444 template <typename F>
445 void call_fn(F&& fn) {
447 Base::call_fn(std::move(fn));
448 } catch (LastException& e) {
449 if (typeid(e) == typeid(LastException&)) {
450 assign_exception(e, std::current_exception());
452 exception_wrapper::assign_eptr(std::current_exception(), e);
459 class try_and_catch<> : public exception_wrapper {
461 try_and_catch() = default;
464 template <typename F>
465 void call_fn(F&& fn) {