Add proper Doxygen comment markings around exception_wrapper example.
[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  * \par Example usage:
65  * \par
66  * \code
67  * exception_wrapper globalExceptionWrapper;
68  *
69  * // Thread1
70  * void doSomethingCrazy() {
71  *   int rc = doSomethingCrazyWithLameReturnCodes();
72  *   if (rc == NAILED_IT) {
73  *     globalExceptionWrapper = exception_wrapper();
74  *   } else if (rc == FACE_PLANT) {
75  *     globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
76  *   } else if (rc == FAIL_WHALE) {
77  *     globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
78  *   }
79  * }
80  *
81  * // Thread2: Exceptions are ok!
82  * void processResult() {
83  *   try {
84  *     globalExceptionWrapper.throwException();
85  *   } catch (const FacePlantException& e) {
86  *     LOG(ERROR) << "FACEPLANT!";
87  *   } catch (const FailWhaleException& e) {
88  *     LOG(ERROR) << "FAILWHALE!";
89  *   }
90  * }
91  *
92  * // Thread2: Exceptions are bad!
93  * void processResult() {
94  *   globalExceptionWrapper.with_exception(
95  *       [&](FacePlantException& faceplant) {
96  *         LOG(ERROR) << "FACEPLANT";
97  *       }) ||
98  *   globalExceptionWrapper.with_exception(
99  *       [&](FailWhaleException& failwhale) {
100  *         LOG(ERROR) << "FAILWHALE!";
101  *       }) ||
102  *   LOG(FATAL) << "Unrecognized exception";
103  * }
104  * \endcode
105  *
106  */
107 class exception_wrapper {
108  protected:
109   template <typename Ex>
110   struct optimize;
111
112  public:
113   exception_wrapper() = default;
114
115   // Implicitly construct an exception_wrapper from a qualifying exception.
116   // See the optimize struct for details.
117   template <typename Ex, typename =
118     typename std::enable_if<optimize<typename std::decay<Ex>::type>::value>
119     ::type>
120   /* implicit */ exception_wrapper(Ex&& exn) {
121     typedef typename std::decay<Ex>::type DEx;
122     assign_sptr(std::make_shared<DEx>(std::forward<Ex>(exn)));
123   }
124
125   // The following two constructors are meant to emulate the behavior of
126   // try_and_catch in performance sensitive code as well as to be flexible
127   // enough to wrap exceptions of unknown type. There is an overload that
128   // takes an exception reference so that the wrapper can extract and store
129   // the exception's type and what() when possible.
130   //
131   // The canonical use case is to construct an all-catching exception wrapper
132   // with minimal overhead like so:
133   //
134   //   try {
135   //     // some throwing code
136   //   } catch (const std::exception& e) {
137   //     // won't lose e's type and what()
138   //     exception_wrapper ew{std::current_exception(), e};
139   //   } catch (...) {
140   //     // everything else
141   //     exception_wrapper ew{std::current_exception()};
142   //   }
143   //
144   // try_and_catch is cleaner and preferable. Use it unless you're sure you need
145   // something like this instead.
146   template <typename Ex>
147   explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
148     assign_eptr(eptr, exn);
149   }
150
151   explicit exception_wrapper(std::exception_ptr eptr) {
152     assign_eptr(eptr);
153   }
154
155   // If the exception_wrapper does not contain an exception, std::terminate()
156   // is invoked to assure the [[noreturn]] behaviour.
157   [[noreturn]] void throwException() const;
158
159   explicit operator bool() const {
160     return item_ || eptr_;
161   }
162
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 {
169     if (item_) {
170       return a.item_ && item_.get() == a.item_.get();
171     } else {
172       return eptr_ == a.eptr_;
173     }
174   }
175
176   bool operator!=(const exception_wrapper& a) const {
177     return !(*this == a);
178   }
179
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(); }
186
187   fbstring what() const;
188   fbstring class_name() const;
189
190   template <class Ex>
191   bool is_compatible_with() const {
192     if (item_) {
193       return dynamic_cast<const Ex*>(item_.get());
194     } else if (eptr_) {
195       try {
196         std::rethrow_exception(eptr_);
197       } catch (typename std::decay<Ex>::type&) {
198         return true;
199       } catch (...) {
200         // fall through
201       }
202     }
203     return false;
204   }
205
206   template <class F>
207   bool with_exception(F&& f) {
208     using arg_type = typename functor_traits<F>::arg_type_decayed;
209     return with_exception<arg_type>(std::forward<F>(f));
210   }
211
212   template <class F>
213   bool with_exception(F&& f) const {
214     using arg_type = typename functor_traits<F>::arg_type_decayed;
215     return with_exception<const arg_type>(std::forward<F>(f));
216   }
217
218   // If this exception wrapper wraps an exception of type Ex, with_exception
219   // will call f with the wrapped exception as an argument and return true, and
220   // will otherwise return false.
221   template <class Ex, class F>
222   typename std::enable_if<
223     std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
224     bool>::type
225   with_exception(F f) {
226     return with_exception1<typename std::decay<Ex>::type>(f, this);
227   }
228
229   // Const overload
230   template <class Ex, class F>
231   typename std::enable_if<
232     std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
233     bool>::type
234   with_exception(F f) const {
235     return with_exception1<const typename std::decay<Ex>::type>(f, this);
236   }
237
238   // Overload for non-exceptions. Always rethrows.
239   template <class Ex, class F>
240   typename std::enable_if<
241     !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
242     bool>::type
243   with_exception(F f) const {
244     try {
245       if (*this) {
246         throwException();
247       }
248     } catch (typename std::decay<Ex>::type& e) {
249       f(e);
250       return true;
251     } catch (...) {
252       // fall through
253     }
254     return false;
255   }
256
257   std::exception_ptr getExceptionPtr() const {
258     if (eptr_) {
259       return eptr_;
260     }
261
262     try {
263       if (*this) {
264         throwException();
265       }
266     } catch (...) {
267       return std::current_exception();
268     }
269     return std::exception_ptr();
270   }
271
272  protected:
273   template <typename Ex>
274   struct optimize {
275     static const bool value =
276       std::is_base_of<std::exception, Ex>::value &&
277       std::is_copy_assignable<Ex>::value &&
278       !std::is_abstract<Ex>::value;
279   };
280
281   template <typename Ex>
282   void assign_sptr(std::shared_ptr<Ex> sptr) {
283     this->item_ = std::move(sptr);
284     this->throwfn_ = Thrower<Ex>::doThrow;
285   }
286
287   template <typename Ex>
288   void assign_eptr(std::exception_ptr eptr, Ex& e) {
289     this->eptr_ = eptr;
290     this->estr_ = exceptionStr(e).toStdString();
291     this->ename_ = demangle(typeid(e)).toStdString();
292   }
293
294   void assign_eptr(std::exception_ptr eptr) {
295     this->eptr_ = eptr;
296   }
297
298   // Optimized case: if we know what type the exception is, we can
299   // store a copy of the concrete type, and a helper function so we
300   // can rethrow it.
301   std::shared_ptr<std::exception> item_;
302   void (*throwfn_)(std::exception&){nullptr};
303   // Fallback case: store the library wrapper, which is less efficient
304   // but gets the job done.  Also store exceptionPtr() the name of the
305   // exception type, so we can at least get those back out without
306   // having to rethrow.
307   std::exception_ptr eptr_;
308   std::string estr_;
309   std::string ename_;
310
311   template <class T, class... Args>
312   friend exception_wrapper make_exception_wrapper(Args&&... args);
313
314  private:
315   template <typename F>
316   struct functor_traits {
317     template <typename T>
318     struct impl;
319     template <typename C, typename R, typename A>
320     struct impl<R(C::*)(A)> { using arg_type = A; };
321     template <typename C, typename R, typename A>
322     struct impl<R(C::*)(A) const> { using arg_type = A; };
323     using functor_decayed = typename std::decay<F>::type;
324     using functor_op = decltype(&functor_decayed::operator());
325     using arg_type = typename impl<functor_op>::arg_type;
326     using arg_type_decayed = typename std::decay<arg_type>::type;
327   };
328
329   template <class T>
330   class Thrower {
331    public:
332     static void doThrow(std::exception& obj) {
333       throw static_cast<T&>(obj);
334     }
335   };
336
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) {
342     if (that->item_) {
343       if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
344         f(*ex);
345         return true;
346       }
347     } else if (that->eptr_) {
348       try {
349         std::rethrow_exception(that->eptr_);
350       } catch (Ex& e) {
351         f(e);
352         return true;
353       } catch (...) {
354         // fall through
355       }
356     }
357     return false;
358   }
359 };
360
361 template <class T, class... Args>
362 exception_wrapper make_exception_wrapper(Args&&... args) {
363   exception_wrapper ew;
364   ew.assign_sptr(std::make_shared<T>(std::forward<Args>(args)...));
365   return ew;
366 }
367
368 // For consistency with exceptionStr() functions in ExceptionString.h
369 fbstring exceptionStr(const exception_wrapper& ew);
370
371 /*
372  * try_and_catch is a simple replacement for try {} catch(){} that allows you to
373  * specify which derived exceptions you would like to catch and store in an
374  * exception_wrapper.
375  *
376  * Because we cannot build an equivalent of std::current_exception(), we need
377  * to catch every derived exception that we are interested in catching.
378  *
379  * Exceptions should be listed in the reverse order that you would write your
380  * catch statements (that is, std::exception& should be first).
381  *
382  * NOTE: Although implemented as a derived class (for syntactic delight), don't
383  * be confused - you should not pass around try_and_catch objects!
384  *
385  * Example Usage:
386  *
387  * // This catches my runtime_error and if I call throwException() on ew, it
388  * // will throw a runtime_error
389  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
390  *   if (badThingHappens()) {
391  *     throw std::runtime_error("ZOMG!");
392  *   }
393  * });
394  *
395  * // This will catch the exception and if I call throwException() on ew, it
396  * // will throw a std::exception
397  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
398  *   if (badThingHappens()) {
399  *     throw std::exception();
400  *   }
401  * });
402  *
403  * // This will not catch the exception and it will be thrown.
404  * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
405  *   if (badThingHappens()) {
406  *     throw std::exception();
407  *   }
408  * });
409  */
410
411 namespace detail {
412
413 template <typename... Exceptions>
414 class try_and_catch;
415
416 template <typename LastException, typename... Exceptions>
417 class try_and_catch<LastException, Exceptions...> :
418     public try_and_catch<Exceptions...> {
419  public:
420   template <typename F>
421   explicit try_and_catch(F&& fn) : Base() {
422     call_fn(fn);
423   }
424
425  protected:
426   typedef try_and_catch<Exceptions...> Base;
427
428   try_and_catch() : Base() {}
429
430   template <typename Ex>
431   typename std::enable_if<!exception_wrapper::optimize<Ex>::value>::type
432   assign_exception(Ex& e, std::exception_ptr eptr) {
433     exception_wrapper::assign_eptr(eptr, e);
434   }
435
436   template <typename Ex>
437   typename std::enable_if<exception_wrapper::optimize<Ex>::value>::type
438   assign_exception(Ex& e, std::exception_ptr /*eptr*/) {
439     exception_wrapper::assign_sptr(std::make_shared<Ex>(e));
440   }
441
442   template <typename F>
443   void call_fn(F&& fn) {
444     try {
445       Base::call_fn(std::move(fn));
446     } catch (LastException& e) {
447       if (typeid(e) == typeid(LastException&)) {
448         assign_exception(e, std::current_exception());
449       } else {
450         exception_wrapper::assign_eptr(std::current_exception(), e);
451       }
452     }
453   }
454 };
455
456 template<>
457 class try_and_catch<> : public exception_wrapper {
458  public:
459   try_and_catch() = default;
460
461  protected:
462   template <typename F>
463   void call_fn(F&& fn) {
464     fn();
465   }
466 };
467 }
468
469 template <typename... Exceptions, typename F>
470 exception_wrapper try_and_catch(F&& fn) {
471   return detail::try_and_catch<Exceptions...>(std::forward<F>(fn));
472 } // detail
473
474 } // folly