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.
23 #include <type_traits>
26 #include <folly/ExceptionString.h>
27 #include <folly/FBString.h>
32 * Throwing exceptions can be a convenient way to handle errors. Storing
33 * exceptions in an exception_ptr makes it easy to handle exceptions in a
34 * different thread or at a later time. exception_ptr can also be used in a very
35 * generic result/exception wrapper.
37 * However, there are some issues with throwing exceptions and
38 * std::exception_ptr. These issues revolve around throw being expensive,
39 * particularly in a multithreaded environment (see
40 * ExceptionWrapperBenchmark.cpp).
42 * Imagine we have a library that has an API which returns a result/exception
43 * wrapper. Let's consider some approaches for implementing this wrapper.
44 * First, we could store a std::exception. This approach loses the derived
45 * exception type, which can make exception handling more difficult for users
46 * that prefer rethrowing the exception. We could use a folly::dynamic for every
47 * possible type of exception. This is not very flexible - adding new types of
48 * exceptions requires a change to the result/exception wrapper. We could use an
49 * exception_ptr. However, constructing an exception_ptr as well as accessing
50 * the error requires a call to throw. That means that there will be two calls
51 * to throw in order to process the exception. For performance sensitive
52 * applications, this may be unacceptable.
54 * exception_wrapper is designed to handle exception management for both
55 * convenience and high performance use cases. make_exception_wrapper is
56 * templated on derived type, allowing us to rethrow the exception properly for
57 * users that prefer convenience. These explicitly named exception types can
58 * therefore be handled without any peformance penalty. exception_wrapper is
59 * also flexible enough to accept any type. If a caught exception is not of an
60 * explicitly named type, then std::exception_ptr is used to preserve the
61 * exception state. For performance sensitive applications, the accessor methods
62 * can test or extract a pointer to a specific exception type with very little
68 * exception_wrapper globalExceptionWrapper;
71 * void doSomethingCrazy() {
72 * int rc = doSomethingCrazyWithLameReturnCodes();
73 * if (rc == NAILED_IT) {
74 * globalExceptionWrapper = exception_wrapper();
75 * } else if (rc == FACE_PLANT) {
76 * globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
77 * } else if (rc == FAIL_WHALE) {
78 * globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
82 * // Thread2: Exceptions are ok!
83 * void processResult() {
85 * globalExceptionWrapper.throwException();
86 * } catch (const FacePlantException& e) {
87 * LOG(ERROR) << "FACEPLANT!";
88 * } catch (const FailWhaleException& e) {
89 * LOG(ERROR) << "FAILWHALE!";
93 * // Thread2: Exceptions are bad!
94 * void processResult() {
95 * globalExceptionWrapper.with_exception(
96 * [&](FacePlantException& faceplant) {
97 * LOG(ERROR) << "FACEPLANT";
99 * globalExceptionWrapper.with_exception(
100 * [&](FailWhaleException& failwhale) {
101 * LOG(ERROR) << "FAILWHALE!";
103 * LOG(FATAL) << "Unrecognized exception";
108 class exception_wrapper {
110 template <typename Ex>
114 exception_wrapper() = default;
116 // Implicitly construct an exception_wrapper from a qualifying exception.
117 // See the optimize struct for details.
118 template <typename Ex, typename =
119 typename std::enable_if<optimize<typename std::decay<Ex>::type>::value>
121 /* implicit */ exception_wrapper(Ex&& exn) {
122 typedef typename std::decay<Ex>::type DEx;
123 assign_sptr(std::make_shared<DEx>(std::forward<Ex>(exn)));
126 // The following two constructors are meant to emulate the behavior of
127 // try_and_catch in performance sensitive code as well as to be flexible
128 // enough to wrap exceptions of unknown type. There is an overload that
129 // takes an exception reference so that the wrapper can extract and store
130 // the exception's type and what() when possible.
132 // The canonical use case is to construct an all-catching exception wrapper
133 // with minimal overhead like so:
136 // // some throwing code
137 // } catch (const std::exception& e) {
138 // // won't lose e's type and what()
139 // exception_wrapper ew{std::current_exception(), e};
141 // // everything else
142 // exception_wrapper ew{std::current_exception()};
145 // try_and_catch is cleaner and preferable. Use it unless you're sure you need
146 // something like this instead.
147 template <typename Ex>
148 explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
149 assign_eptr(eptr, exn);
152 explicit exception_wrapper(std::exception_ptr eptr) {
156 // If the exception_wrapper does not contain an exception, std::terminate()
157 // is invoked to assure the [[noreturn]] behaviour.
158 [[noreturn]] void throwException() const;
160 explicit operator bool() const {
161 return item_ || eptr_;
164 // This implementation is similar to std::exception_ptr's implementation
165 // where two exception_wrappers are equal when the address in the underlying
166 // reference field both point to the same exception object. The reference
167 // field remains the same when the exception_wrapper is copied or when
168 // the exception_wrapper is "rethrown".
169 bool operator==(const exception_wrapper& a) const {
171 return a.item_ && item_.get() == a.item_.get();
173 return eptr_ == a.eptr_;
177 bool operator!=(const exception_wrapper& a) const {
178 return !(*this == a);
181 // This will return a non-nullptr only if the exception is held as a
182 // copy. It is the only interface which will distinguish between an
183 // exception held this way, and by exception_ptr. You probably
184 // shouldn't use it at all.
185 std::exception* getCopied() { return item_.get(); }
186 const std::exception* getCopied() const { return item_.get(); }
188 fbstring what() const;
189 fbstring class_name() const;
192 bool is_compatible_with() const {
194 return dynamic_cast<const Ex*>(item_.get());
197 std::rethrow_exception(eptr_);
198 } catch (typename std::decay<Ex>::type&) {
208 bool with_exception(F&& f) {
209 using arg_type = typename functor_traits<F>::arg_type_decayed;
210 return with_exception<arg_type>(std::forward<F>(f));
214 bool with_exception(F&& f) const {
215 using arg_type = typename functor_traits<F>::arg_type_decayed;
216 return with_exception<const arg_type>(std::forward<F>(f));
219 // If this exception wrapper wraps an exception of type Ex, with_exception
220 // will call f with the wrapped exception as an argument and return true, and
221 // will otherwise return false.
222 template <class Ex, class F>
223 typename std::enable_if<
224 std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
226 with_exception(F f) {
227 return with_exception1<typename std::decay<Ex>::type>(f, this);
231 template <class Ex, class F>
232 typename std::enable_if<
233 std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
235 with_exception(F f) const {
236 return with_exception1<const typename std::decay<Ex>::type>(f, this);
239 // Overload for non-exceptions. Always rethrows.
240 template <class Ex, class F>
241 typename std::enable_if<
242 !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
244 with_exception(F f) const {
249 } catch (typename std::decay<Ex>::type& e) {
258 std::exception_ptr getExceptionPtr() const {
268 return std::current_exception();
270 return std::exception_ptr();
274 template <typename Ex>
276 static const bool value =
277 std::is_base_of<std::exception, Ex>::value &&
278 std::is_copy_assignable<Ex>::value &&
279 !std::is_abstract<Ex>::value;
282 template <typename Ex>
283 void assign_sptr(std::shared_ptr<Ex> sptr) {
284 this->item_ = std::move(sptr);
285 this->throwfn_ = Thrower<Ex>::doThrow;
288 template <typename Ex>
289 void assign_eptr(std::exception_ptr eptr, Ex& e) {
291 this->estr_ = exceptionStr(e).toStdString();
292 this->ename_ = demangle(typeid(e)).toStdString();
295 void assign_eptr(std::exception_ptr eptr) {
299 // Optimized case: if we know what type the exception is, we can
300 // store a copy of the concrete type, and a helper function so we
302 std::shared_ptr<std::exception> item_;
303 void (*throwfn_)(std::exception&){nullptr};
304 // Fallback case: store the library wrapper, which is less efficient
305 // but gets the job done. Also store exceptionPtr() the name of the
306 // exception type, so we can at least get those back out without
307 // having to rethrow.
308 std::exception_ptr eptr_;
312 template <class T, class... Args>
313 friend exception_wrapper make_exception_wrapper(Args&&... args);
316 template <typename F>
317 struct functor_traits {
318 template <typename T>
320 template <typename C, typename R, typename A>
321 struct impl<R(C::*)(A)> { using arg_type = A; };
322 template <typename C, typename R, typename A>
323 struct impl<R(C::*)(A) const> { using arg_type = A; };
324 using functor_decayed = typename std::decay<F>::type;
325 using functor_op = decltype(&functor_decayed::operator());
326 using arg_type = typename impl<functor_op>::arg_type;
327 using arg_type_decayed = typename std::decay<arg_type>::type;
333 static void doThrow(std::exception& obj) {
334 throw static_cast<T&>(obj);
338 // What makes this useful is that T can be exception_wrapper* or
339 // const exception_wrapper*, and the compiler will use the
340 // instantiation which works with F.
341 template <class Ex, class F, class T>
342 static bool with_exception1(F f, T* that) {
344 if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
348 } else if (that->eptr_) {
350 std::rethrow_exception(that->eptr_);
362 template <class T, class... Args>
363 exception_wrapper make_exception_wrapper(Args&&... args) {
364 exception_wrapper ew;
365 ew.assign_sptr(std::make_shared<T>(std::forward<Args>(args)...));
369 // For consistency with exceptionStr() functions in ExceptionString.h
370 fbstring exceptionStr(const exception_wrapper& ew);
373 * try_and_catch is a simple replacement for try {} catch(){} that allows you to
374 * specify which derived exceptions you would like to catch and store in an
377 * Because we cannot build an equivalent of std::current_exception(), we need
378 * to catch every derived exception that we are interested in catching.
380 * Exceptions should be listed in the reverse order that you would write your
381 * catch statements (that is, std::exception& should be first).
383 * NOTE: Although implemented as a derived class (for syntactic delight), don't
384 * be confused - you should not pass around try_and_catch objects!
388 * // This catches my runtime_error and if I call throwException() on ew, it
389 * // will throw a runtime_error
390 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
391 * if (badThingHappens()) {
392 * throw std::runtime_error("ZOMG!");
396 * // This will catch the exception and if I call throwException() on ew, it
397 * // will throw a std::exception
398 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
399 * if (badThingHappens()) {
400 * throw std::exception();
404 * // This will not catch the exception and it will be thrown.
405 * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
406 * if (badThingHappens()) {
407 * throw std::exception();
412 namespace try_and_catch_detail {
414 template <bool V, typename T = void>
415 using enable_if_t_ = typename std::enable_if<V, T>::type;
417 template <typename... Args>
418 using is_wrap_ctor = std::is_constructible<exception_wrapper, Args...>;
420 template <typename Ex>
421 inline enable_if_t_<!is_wrap_ctor<Ex&>::value, exception_wrapper> make(Ex& ex) {
422 return exception_wrapper(std::current_exception(), ex);
425 template <typename Ex>
426 inline enable_if_t_<is_wrap_ctor<Ex&>::value, exception_wrapper> make(Ex& ex) {
427 return typeid(Ex&) == typeid(ex)
428 ? exception_wrapper(ex)
429 : exception_wrapper(std::current_exception(), ex);
432 template <typename F>
433 inline exception_wrapper impl(F&& f) {
434 return (f(), exception_wrapper());
437 template <typename F, typename Ex, typename... Exs>
438 inline exception_wrapper impl(F&& f) {
440 return impl<F, Exs...>(std::forward<F>(f));
445 } // try_and_catch_detail
447 template <typename... Exceptions, typename F>
448 exception_wrapper try_and_catch(F&& fn) {
449 return try_and_catch_detail::impl<F, Exceptions...>(std::forward<F>(fn));