2 * Copyright 2017 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>
28 #include <folly/Traits.h>
33 * Throwing exceptions can be a convenient way to handle errors. Storing
34 * exceptions in an exception_ptr makes it easy to handle exceptions in a
35 * different thread or at a later time. exception_ptr can also be used in a very
36 * generic result/exception wrapper.
38 * However, there are some issues with throwing exceptions and
39 * std::exception_ptr. These issues revolve around throw being expensive,
40 * particularly in a multithreaded environment (see
41 * ExceptionWrapperBenchmark.cpp).
43 * Imagine we have a library that has an API which returns a result/exception
44 * wrapper. Let's consider some approaches for implementing this wrapper.
45 * First, we could store a std::exception. This approach loses the derived
46 * exception type, which can make exception handling more difficult for users
47 * that prefer rethrowing the exception. We could use a folly::dynamic for every
48 * possible type of exception. This is not very flexible - adding new types of
49 * exceptions requires a change to the result/exception wrapper. We could use an
50 * exception_ptr. However, constructing an exception_ptr as well as accessing
51 * the error requires a call to throw. That means that there will be two calls
52 * to throw in order to process the exception. For performance sensitive
53 * applications, this may be unacceptable.
55 * exception_wrapper is designed to handle exception management for both
56 * convenience and high performance use cases. make_exception_wrapper is
57 * templated on derived type, allowing us to rethrow the exception properly for
58 * users that prefer convenience. These explicitly named exception types can
59 * therefore be handled without any peformance penalty. exception_wrapper is
60 * also flexible enough to accept any type. If a caught exception is not of an
61 * explicitly named type, then std::exception_ptr is used to preserve the
62 * exception state. For performance sensitive applications, the accessor methods
63 * can test or extract a pointer to a specific exception type with very little
69 * exception_wrapper globalExceptionWrapper;
72 * void doSomethingCrazy() {
73 * int rc = doSomethingCrazyWithLameReturnCodes();
74 * if (rc == NAILED_IT) {
75 * globalExceptionWrapper = exception_wrapper();
76 * } else if (rc == FACE_PLANT) {
77 * globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
78 * } else if (rc == FAIL_WHALE) {
79 * globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
83 * // Thread2: Exceptions are ok!
84 * void processResult() {
86 * globalExceptionWrapper.throwException();
87 * } catch (const FacePlantException& e) {
88 * LOG(ERROR) << "FACEPLANT!";
89 * } catch (const FailWhaleException& e) {
90 * LOG(ERROR) << "FAILWHALE!";
94 * // Thread2: Exceptions are bad!
95 * void processResult() {
96 * globalExceptionWrapper.with_exception(
97 * [&](FacePlantException& faceplant) {
98 * LOG(ERROR) << "FACEPLANT";
100 * globalExceptionWrapper.with_exception(
101 * [&](FailWhaleException& failwhale) {
102 * LOG(ERROR) << "FAILWHALE!";
104 * LOG(FATAL) << "Unrecognized exception";
109 class exception_wrapper {
111 template <typename T>
112 using is_exception_ = std::is_base_of<std::exception, T>;
114 template <typename Ex>
118 exception_wrapper() = default;
120 // Implicitly construct an exception_wrapper from a qualifying exception.
121 // See the optimize struct for details.
124 typename = _t<std::enable_if<optimize<_t<std::decay<Ex>>>::value>>>
125 /* implicit */ exception_wrapper(Ex&& exn) {
126 assign_sptr(std::make_shared<_t<std::decay<Ex>>>(std::forward<Ex>(exn)));
129 // The following two constructors are meant to emulate the behavior of
130 // try_and_catch in performance sensitive code as well as to be flexible
131 // enough to wrap exceptions of unknown type. There is an overload that
132 // takes an exception reference so that the wrapper can extract and store
133 // the exception's type and what() when possible.
135 // The canonical use case is to construct an all-catching exception wrapper
136 // with minimal overhead like so:
139 // // some throwing code
140 // } catch (const std::exception& e) {
141 // // won't lose e's type and what()
142 // exception_wrapper ew{std::current_exception(), e};
144 // // everything else
145 // exception_wrapper ew{std::current_exception()};
148 // try_and_catch is cleaner and preferable. Use it unless you're sure you need
149 // something like this instead.
150 template <typename Ex>
151 explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
152 assign_eptr(eptr, exn);
155 explicit exception_wrapper(std::exception_ptr eptr) {
159 // If the exception_wrapper does not contain an exception, std::terminate()
160 // is invoked to assure the [[noreturn]] behaviour.
161 [[noreturn]] void throwException() const;
163 explicit operator bool() const {
164 return item_ || eptr_;
167 // This implementation is similar to std::exception_ptr's implementation
168 // where two exception_wrappers are equal when the address in the underlying
169 // reference field both point to the same exception object. The reference
170 // field remains the same when the exception_wrapper is copied or when
171 // the exception_wrapper is "rethrown".
172 bool operator==(const exception_wrapper& a) const {
174 return a.item_ && item_.get() == a.item_.get();
176 return eptr_ == a.eptr_;
180 bool operator!=(const exception_wrapper& a) const {
181 return !(*this == a);
184 // This will return a non-nullptr only if the exception is held as a
185 // copy. It is the only interface which will distinguish between an
186 // exception held this way, and by exception_ptr. You probably
187 // shouldn't use it at all.
188 std::exception* getCopied() { return item_.get(); }
189 const std::exception* getCopied() const { return item_.get(); }
191 fbstring what() const;
192 fbstring class_name() const;
195 bool is_compatible_with() const {
196 return with_exception<Ex>([](const Ex&) {});
200 bool with_exception(F&& f) {
201 using arg_type = _t<std::decay<typename functor_traits<F>::arg_type>>;
202 return with_exception<arg_type>(std::forward<F>(f));
206 bool with_exception(F&& f) const {
207 using arg_type = _t<std::decay<typename functor_traits<F>::arg_type>>;
208 return with_exception<arg_type>(std::forward<F>(f));
211 // If this exception wrapper wraps an exception of type Ex, with_exception
212 // will call f with the wrapped exception as an argument and return true, and
213 // will otherwise return false.
214 template <class Ex, class F>
215 bool with_exception(F f) {
216 return with_exception1<_t<std::decay<Ex>>>(std::forward<F>(f), this);
220 template <class Ex, class F>
221 bool with_exception(F f) const {
222 return with_exception1<_t<std::decay<Ex>>>(std::forward<F>(f), this);
225 std::exception_ptr getExceptionPtr() const {
235 return std::current_exception();
237 return std::exception_ptr();
241 template <typename Ex>
243 static const bool value =
244 std::is_base_of<std::exception, Ex>::value &&
245 std::is_copy_assignable<Ex>::value &&
246 !std::is_abstract<Ex>::value;
249 template <typename Ex>
250 void assign_sptr(std::shared_ptr<Ex> sptr) {
251 this->item_ = std::move(sptr);
252 this->throwfn_ = Thrower<Ex>::doThrow;
255 template <typename Ex>
256 _t<std::enable_if<is_exception_<Ex>::value>> assign_eptr(
257 std::exception_ptr eptr,
260 this->eobj_ = &const_cast<_t<std::remove_const<Ex>>&>(e);
263 template <typename Ex>
264 _t<std::enable_if<!is_exception_<Ex>::value>> assign_eptr(
265 std::exception_ptr eptr,
268 this->etype_ = &typeid(e);
271 void assign_eptr(std::exception_ptr eptr) {
275 // Optimized case: if we know what type the exception is, we can
276 // store a copy of the concrete type, and a helper function so we
278 std::shared_ptr<std::exception> item_;
279 void (*throwfn_)(std::exception&){nullptr};
280 // Fallback case: store the library wrapper, which is less efficient
281 // but gets the job done. Also store exceptionPtr() the name of the
282 // exception type, so we can at least get those back out without
283 // having to rethrow.
284 std::exception_ptr eptr_;
285 std::exception* eobj_{nullptr};
286 const std::type_info* etype_{nullptr};
288 template <class T, class... Args>
289 friend exception_wrapper make_exception_wrapper(Args&&... args);
292 template <typename F>
293 struct functor_traits {
294 template <typename T>
296 template <typename C, typename R, typename A>
297 struct impl<R(C::*)(A)> { using arg_type = A; };
298 template <typename C, typename R, typename A>
299 struct impl<R(C::*)(A) const> { using arg_type = A; };
300 using functor_op = decltype(&_t<std::decay<F>>::operator());
301 using arg_type = typename impl<functor_op>::arg_type;
307 static void doThrow(std::exception& obj) {
308 throw static_cast<T&>(obj);
312 template <typename T, typename F>
313 static _t<std::enable_if<is_exception_<T>::value, T*>>
314 try_dynamic_cast_exception(F* from) {
315 return dynamic_cast<T*>(from);
317 template <typename T, typename F>
318 static _t<std::enable_if<!is_exception_<T>::value, T*>>
319 try_dynamic_cast_exception(F*) {
323 // What makes this useful is that T can be exception_wrapper* or
324 // const exception_wrapper*, and the compiler will use the
325 // instantiation which works with F.
326 template <class Ex, class F, class T>
327 static bool with_exception1(F f, T* that) {
328 using CEx = _t<std::conditional<std::is_const<T>::value, const Ex, Ex>>;
329 if (is_exception_<Ex>::value &&
330 (that->item_ || (that->eptr_ && that->eobj_))) {
332 that->item_ ? that->item_.get() : that->eptr_ ? that->eobj_ : nullptr;
333 if (auto ex = try_dynamic_cast_exception<CEx>(raw)) {
337 } else if (that->eptr_) {
339 std::rethrow_exception(that->eptr_);
351 template <class T, class... Args>
352 exception_wrapper make_exception_wrapper(Args&&... args) {
353 exception_wrapper ew;
354 ew.assign_sptr(std::make_shared<T>(std::forward<Args>(args)...));
358 // For consistency with exceptionStr() functions in ExceptionString.h
359 fbstring exceptionStr(const exception_wrapper& ew);
362 * try_and_catch is a simple replacement for try {} catch(){} that allows you to
363 * specify which derived exceptions you would like to catch and store in an
366 * Because we cannot build an equivalent of std::current_exception(), we need
367 * to catch every derived exception that we are interested in catching.
369 * Exceptions should be listed in the reverse order that you would write your
370 * catch statements (that is, std::exception& should be first).
372 * NOTE: Although implemented as a derived class (for syntactic delight), don't
373 * be confused - you should not pass around try_and_catch objects!
377 * // This catches my runtime_error and if I call throwException() on ew, it
378 * // will throw a runtime_error
379 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
380 * if (badThingHappens()) {
381 * throw std::runtime_error("ZOMG!");
385 * // This will catch the exception and if I call throwException() on ew, it
386 * // will throw a std::exception
387 * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
388 * if (badThingHappens()) {
389 * throw std::exception();
393 * // This will not catch the exception and it will be thrown.
394 * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
395 * if (badThingHappens()) {
396 * throw std::exception();
401 namespace try_and_catch_detail {
403 template <typename... Args>
404 using is_wrap_ctor = std::is_constructible<exception_wrapper, Args...>;
406 template <typename Ex>
407 inline _t<std::enable_if<!is_wrap_ctor<Ex&>::value, exception_wrapper>> make(
409 return exception_wrapper(std::current_exception(), ex);
412 template <typename Ex>
413 inline _t<std::enable_if<is_wrap_ctor<Ex&>::value, exception_wrapper>> make(
415 return typeid(Ex&) == typeid(ex)
416 ? exception_wrapper(ex)
417 : exception_wrapper(std::current_exception(), ex);
420 template <typename F>
421 inline exception_wrapper impl(F&& f) {
422 return (f(), exception_wrapper());
425 template <typename F, typename Ex, typename... Exs>
426 inline exception_wrapper impl(F&& f) {
428 return impl<F, Exs...>(std::forward<F>(f));
433 } // try_and_catch_detail
435 template <typename... Exceptions, typename F>
436 exception_wrapper try_and_catch(F&& fn) {
437 return try_and_catch_detail::impl<F, Exceptions...>(std::forward<F>(fn));