Fix a wrong filename in a comment in ExceptionWrapper.h
[folly.git] / folly / ExceptionWrapper.h
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #pragma once
18
19 #include <exception>
20 #include <memory>
21 #include <string>
22 #include <type_traits>
23 #include <utility>
24
25 #include <folly/ExceptionString.h>
26 #include <folly/FBString.h>
27
28 namespace folly {
29
30 /*
31  * Throwing exceptions can be a convenient way to handle errors. Storing
32  * exceptions in an exception_ptr makes it easy to handle exceptions in a
33  * different thread or at a later time. exception_ptr can also be used in a very
34  * generic result/exception wrapper.
35  *
36  * However, there are some issues with throwing exceptions and
37  * std::exception_ptr. These issues revolve around throw being expensive,
38  * particularly in a multithreaded environment (see
39  * ExceptionWrapperBenchmark.cpp).
40  *
41  * Imagine we have a library that has an API which returns a result/exception
42  * wrapper. Let's consider some approaches for implementing this wrapper.
43  * First, we could store a std::exception. This approach loses the derived
44  * exception type, which can make exception handling more difficult for users
45  * that prefer rethrowing the exception. We could use a folly::dynamic for every
46  * possible type of exception. This is not very flexible - adding new types of
47  * exceptions requires a change to the result/exception wrapper. We could use an
48  * exception_ptr. However, constructing an exception_ptr as well as accessing
49  * the error requires a call to throw. That means that there will be two calls
50  * to throw in order to process the exception. For performance sensitive
51  * applications, this may be unacceptable.
52  *
53  * exception_wrapper is designed to handle exception management for both
54  * convenience and high performance use cases. make_exception_wrapper is
55  * templated on derived type, allowing us to rethrow the exception properly for
56  * users that prefer convenience. These explicitly named exception types can
57  * therefore be handled without any peformance penalty.  exception_wrapper is
58  * also flexible enough to accept any type. If a caught exception is not of an
59  * explicitly named type, then std::exception_ptr is used to preserve the
60  * exception state. For performance sensitive applications, the accessor methods
61  * can test or extract a pointer to a specific exception type with very little
62  * overhead.
63  *
64  * Example usage:
65  *
66  * exception_wrapper globalExceptionWrapper;
67  *
68  * // Thread1
69  * void doSomethingCrazy() {
70  *   int rc = doSomethingCrazyWithLameReturnCodes();
71  *   if (rc == NAILED_IT) {
72  *     globalExceptionWrapper = exception_wrapper();
73  *   } else if (rc == FACE_PLANT) {
74  *     globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
75  *   } else if (rc == FAIL_WHALE) {
76  *     globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
77  *   }
78  * }
79  *
80  * // Thread2: Exceptions are ok!
81  * void processResult() {
82  *   try {
83  *     globalExceptionWrapper.throwException();
84  *   } catch (const FacePlantException& e) {
85  *     LOG(ERROR) << "FACEPLANT!";
86  *   } catch (const FailWhaleException& e) {
87  *     LOG(ERROR) << "FAILWHALE!";
88  *   }
89  * }
90  *
91  * // Thread2: Exceptions are bad!
92  * void processResult() {
93  *   globalExceptionWrapper.with_exception(
94  *       [&](FacePlantException& faceplant) {
95  *         LOG(ERROR) << "FACEPLANT";
96  *       }) ||
97  *   globalExceptionWrapper.with_exception(
98  *       [&](FailWhaleException& failwhale) {
99  *         LOG(ERROR) << "FAILWHALE!";
100  *       }) ||
101  *   LOG(FATAL) << "Unrecognized exception";
102  * }
103  *
104  */
105 class exception_wrapper {
106  protected:
107   template <typename Ex>
108   struct optimize;
109
110  public:
111   exception_wrapper() = default;
112
113   // Implicitly construct an exception_wrapper from a qualifying exception.
114   // See the optimize struct for details.
115   template <typename Ex, typename =
116     typename std::enable_if<optimize<typename std::decay<Ex>::type>::value>
117     ::type>
118   /* implicit */ exception_wrapper(Ex&& exn) {
119     typedef typename std::decay<Ex>::type DEx;
120     assign_sptr(std::make_shared<DEx>(std::forward<Ex>(exn)));
121   }
122
123   // The following two constructors are meant to emulate the behavior of
124   // try_and_catch in performance sensitive code as well as to be flexible
125   // enough to wrap exceptions of unknown type. There is an overload that
126   // takes an exception reference so that the wrapper can extract and store
127   // the exception's type and what() when possible.
128   //
129   // The canonical use case is to construct an all-catching exception wrapper
130   // with minimal overhead like so:
131   //
132   //   try {
133   //     // some throwing code
134   //   } catch (const std::exception& e) {
135   //     // won't lose e's type and what()
136   //     exception_wrapper ew{std::current_exception(), e};
137   //   } catch (...) {
138   //     // everything else
139   //     exception_wrapper ew{std::current_exception()};
140   //   }
141   //
142   // try_and_catch is cleaner and preferable. Use it unless you're sure you need
143   // something like this instead.
144   template <typename Ex>
145   explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
146     assign_eptr(eptr, exn);
147   }
148
149   explicit exception_wrapper(std::exception_ptr eptr) {
150     assign_eptr(eptr);
151   }
152
153   // If the exception_wrapper does not contain an exception, std::terminate()
154   // is invoked to assure the [[noreturn]] behaviour.
155   [[noreturn]] void throwException() const;
156
157   explicit operator bool() const {
158     return item_ || eptr_;
159   }
160
161   // This implementation is similar to std::exception_ptr's implementation
162   // where two exception_wrappers are equal when the address in the underlying
163   // reference field both point to the same exception object.  The reference
164   // field remains the same when the exception_wrapper is copied or when
165   // the exception_wrapper is "rethrown".
166   bool operator==(const exception_wrapper& a) const {
167     if (item_) {
168       return a.item_ && item_.get() == a.item_.get();
169     } else {
170       return eptr_ == a.eptr_;
171     }
172   }
173
174   bool operator!=(const exception_wrapper& a) const {
175     return !(*this == a);
176   }
177
178   // This will return a non-nullptr only if the exception is held as a
179   // copy.  It is the only interface which will distinguish between an
180   // exception held this way, and by exception_ptr.  You probably
181   // shouldn't use it at all.
182   std::exception* getCopied() { return item_.get(); }
183   const std::exception* getCopied() const { return item_.get(); }
184
185   fbstring what() const;
186   fbstring class_name() const;
187
188   template <class Ex>
189   bool is_compatible_with() const {
190     if (item_) {
191       return dynamic_cast<const Ex*>(item_.get());
192     } else if (eptr_) {
193       try {
194         std::rethrow_exception(eptr_);
195       } catch (typename std::decay<Ex>::type&) {
196         return true;
197       } catch (...) {
198         // fall through
199       }
200     }
201     return false;
202   }
203
204   template <class F>
205   bool with_exception(F&& f) {
206     using arg_type = typename functor_traits<F>::arg_type_decayed;
207     return with_exception<arg_type>(std::forward<F>(f));
208   }
209
210   template <class F>
211   bool with_exception(F&& f) const {
212     using arg_type = typename functor_traits<F>::arg_type_decayed;
213     return with_exception<const arg_type>(std::forward<F>(f));
214   }
215
216   // If this exception wrapper wraps an exception of type Ex, with_exception
217   // will call f with the wrapped exception as an argument and return true, and
218   // will otherwise return false.
219   template <class Ex, class F>
220   typename std::enable_if<
221     std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
222     bool>::type
223   with_exception(F f) {
224     return with_exception1<typename std::decay<Ex>::type>(f, this);
225   }
226
227   // Const overload
228   template <class Ex, class F>
229   typename std::enable_if<
230     std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
231     bool>::type
232   with_exception(F f) const {
233     return with_exception1<const typename std::decay<Ex>::type>(f, this);
234   }
235
236   // Overload for non-exceptions. Always rethrows.
237   template <class Ex, class F>
238   typename std::enable_if<
239     !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
240     bool>::type
241   with_exception(F f) const {
242     try {
243       if (*this) {
244         throwException();
245       }
246     } catch (typename std::decay<Ex>::type& e) {
247       f(e);
248       return true;
249     } catch (...) {
250       // fall through
251     }
252     return false;
253   }
254
255   std::exception_ptr getExceptionPtr() const {
256     if (eptr_) {
257       return eptr_;
258     }
259
260     try {
261       if (*this) {
262         throwException();
263       }
264     } catch (...) {
265       return std::current_exception();
266     }
267     return std::exception_ptr();
268   }
269
270  protected:
271   template <typename Ex>
272   struct optimize {
273     static const bool value =
274       std::is_base_of<std::exception, Ex>::value &&
275       std::is_copy_assignable<Ex>::value &&
276       !std::is_abstract<Ex>::value;
277   };
278
279   template <typename Ex>
280   void assign_sptr(std::shared_ptr<Ex> sptr) {
281     this->item_ = std::move(sptr);
282     this->throwfn_ = Thrower<Ex>::doThrow;
283   }
284
285   template <typename Ex>
286   void assign_eptr(std::exception_ptr eptr, Ex& e) {
287     this->eptr_ = eptr;
288     this->estr_ = exceptionStr(e).toStdString();
289     this->ename_ = demangle(typeid(e)).toStdString();
290   }
291
292   void assign_eptr(std::exception_ptr eptr) {
293     this->eptr_ = eptr;
294   }
295
296   // Optimized case: if we know what type the exception is, we can
297   // store a copy of the concrete type, and a helper function so we
298   // can rethrow it.
299   std::shared_ptr<std::exception> item_;
300   void (*throwfn_)(std::exception&){nullptr};
301   // Fallback case: store the library wrapper, which is less efficient
302   // but gets the job done.  Also store exceptionPtr() the name of the
303   // exception type, so we can at least get those back out without
304   // having to rethrow.
305   std::exception_ptr eptr_;
306   std::string estr_;
307   std::string ename_;
308
309   template <class T, class... Args>
310   friend exception_wrapper make_exception_wrapper(Args&&... args);
311
312  private:
313   template <typename F>
314   struct functor_traits {
315     template <typename T>
316     struct impl;
317     template <typename C, typename R, typename A>
318     struct impl<R(C::*)(A)> { using arg_type = A; };
319     template <typename C, typename R, typename A>
320     struct impl<R(C::*)(A) const> { using arg_type = A; };
321     using functor_decayed = typename std::decay<F>::type;
322     using functor_op = decltype(&functor_decayed::operator());
323     using arg_type = typename impl<functor_op>::arg_type;
324     using arg_type_decayed = typename std::decay<arg_type>::type;
325   };
326
327   template <class T>
328   class Thrower {
329    public:
330     static void doThrow(std::exception& obj) {
331       throw static_cast<T&>(obj);
332     }
333   };
334
335   // What makes this useful is that T can be exception_wrapper* or
336   // const exception_wrapper*, and the compiler will use the
337   // instantiation which works with F.
338   template <class Ex, class F, class T>
339   static bool with_exception1(F f, T* that) {
340     if (that->item_) {
341       if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
342         f(*ex);
343         return true;
344       }
345     } else if (that->eptr_) {
346       try {
347         std::rethrow_exception(that->eptr_);
348       } catch (Ex& e) {
349         f(e);
350         return true;
351       } catch (...) {
352         // fall through
353       }
354     }
355     return false;
356   }
357 };
358
359 template <class T, class... Args>
360 exception_wrapper make_exception_wrapper(Args&&... args) {
361   exception_wrapper ew;
362   ew.assign_sptr(std::make_shared<T>(std::forward<Args>(args)...));
363   return ew;
364 }
365
366 // For consistency with exceptionStr() functions in ExceptionString.h
367 fbstring exceptionStr(const exception_wrapper& ew);
368
369 /*
370  * try_and_catch is a simple replacement for try {} catch(){} that allows you to
371  * specify which derived exceptions you would like to catch and store in an
372  * exception_wrapper.
373  *
374  * Because we cannot build an equivalent of std::current_exception(), we need
375  * to catch every derived exception that we are interested in catching.
376  *
377  * Exceptions should be listed in the reverse order that you would write your
378  * catch statements (that is, std::exception& should be first).
379  *
380  * NOTE: Although implemented as a derived class (for syntactic delight), don't
381  * be confused - you should not pass around try_and_catch objects!
382  *
383  * Example Usage:
384  *
385  * // This catches my runtime_error and if I call throwException() on ew, it
386  * // will throw a runtime_error
387  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
388  *   if (badThingHappens()) {
389  *     throw std::runtime_error("ZOMG!");
390  *   }
391  * });
392  *
393  * // This will catch the exception and if I call throwException() on ew, it
394  * // will throw a std::exception
395  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
396  *   if (badThingHappens()) {
397  *     throw std::exception();
398  *   }
399  * });
400  *
401  * // This will not catch the exception and it will be thrown.
402  * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
403  *   if (badThingHappens()) {
404  *     throw std::exception();
405  *   }
406  * });
407  */
408
409 namespace detail {
410
411 template <typename... Exceptions>
412 class try_and_catch;
413
414 template <typename LastException, typename... Exceptions>
415 class try_and_catch<LastException, Exceptions...> :
416     public try_and_catch<Exceptions...> {
417  public:
418   template <typename F>
419   explicit try_and_catch(F&& fn) : Base() {
420     call_fn(fn);
421   }
422
423  protected:
424   typedef try_and_catch<Exceptions...> Base;
425
426   try_and_catch() : Base() {}
427
428   template <typename Ex>
429   typename std::enable_if<!exception_wrapper::optimize<Ex>::value>::type
430   assign_exception(Ex& e, std::exception_ptr eptr) {
431     exception_wrapper::assign_eptr(eptr, e);
432   }
433
434   template <typename Ex>
435   typename std::enable_if<exception_wrapper::optimize<Ex>::value>::type
436   assign_exception(Ex& e, std::exception_ptr /*eptr*/) {
437     exception_wrapper::assign_sptr(std::make_shared<Ex>(e));
438   }
439
440   template <typename F>
441   void call_fn(F&& fn) {
442     try {
443       Base::call_fn(std::move(fn));
444     } catch (LastException& e) {
445       if (typeid(e) == typeid(LastException&)) {
446         assign_exception(e, std::current_exception());
447       } else {
448         exception_wrapper::assign_eptr(std::current_exception(), e);
449       }
450     }
451   }
452 };
453
454 template<>
455 class try_and_catch<> : public exception_wrapper {
456  public:
457   try_and_catch() = default;
458
459  protected:
460   template <typename F>
461   void call_fn(F&& fn) {
462     fn();
463   }
464 };
465 }
466
467 template <typename... Exceptions, typename F>
468 exception_wrapper try_and_catch(F&& fn) {
469   return detail::try_and_catch<Exceptions...>(std::forward<F>(fn));
470 } // detail
471
472 } // folly