implicit constructor exception_wrapper(std::exception)
[folly.git] / folly / ExceptionWrapper.h
1 /*
2  * Copyright 2014 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 #ifndef FOLLY_EXCEPTIONWRAPPER_H
18 #define FOLLY_EXCEPTIONWRAPPER_H
19
20 #include <cassert>
21 #include <exception>
22 #include <memory>
23 #include <folly/String.h>
24 #include <folly/detail/ExceptionWrapper.h>
25
26 namespace folly {
27
28 /*
29  * Throwing exceptions can be a convenient way to handle errors. Storing
30  * exceptions in an exception_ptr makes it easy to handle exceptions in a
31  * different thread or at a later time. exception_ptr can also be used in a very
32  * generic result/exception wrapper.
33  *
34  * However, there are some issues with throwing exceptions and
35  * std::exception_ptr. These issues revolve around throw being expensive,
36  * particularly in a multithreaded environment (see
37  * ExceptionWrapperBenchmark.cpp).
38  *
39  * Imagine we have a library that has an API which returns a result/exception
40  * wrapper. Let's consider some approaches for implementing this wrapper.
41  * First, we could store a std::exception. This approach loses the derived
42  * exception type, which can make exception handling more difficult for users
43  * that prefer rethrowing the exception. We could use a folly::dynamic for every
44  * possible type of exception. This is not very flexible - adding new types of
45  * exceptions requires a change to the result/exception wrapper. We could use an
46  * exception_ptr. However, constructing an exception_ptr as well as accessing
47  * the error requires a call to throw. That means that there will be two calls
48  * to throw in order to process the exception. For performance sensitive
49  * applications, this may be unacceptable.
50  *
51  * exception_wrapper is designed to handle exception management for both
52  * convenience and high performance use cases. make_exception_wrapper is
53  * templated on derived type, allowing us to rethrow the exception properly for
54  * users that prefer convenience. These explicitly named exception types can
55  * therefore be handled without any peformance penalty.  exception_wrapper is
56  * also flexible enough to accept any type. If a caught exception is not of an
57  * explicitly named type, then std::exception_ptr is used to preserve the
58  * exception state. For performance sensitive applications, the accessor methods
59  * can test or extract a pointer to a specific exception type with very little
60  * overhead.
61  *
62  * Example usage:
63  *
64  * exception_wrapper globalExceptionWrapper;
65  *
66  * // Thread1
67  * void doSomethingCrazy() {
68  *   int rc = doSomethingCrazyWithLameReturnCodes();
69  *   if (rc == NAILED_IT) {
70  *     globalExceptionWrapper = exception_wrapper();
71  *   } else if (rc == FACE_PLANT) {
72  *     globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
73  *   } else if (rc == FAIL_WHALE) {
74  *     globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
75  *   }
76  * }
77  *
78  * // Thread2: Exceptions are ok!
79  * void processResult() {
80  *   try {
81  *     globalExceptionWrapper.throwException();
82  *   } catch (const FacePlantException& e) {
83  *     LOG(ERROR) << "FACEPLANT!";
84  *   } catch (const FailWhaleException& e) {
85  *     LOG(ERROR) << "FAILWHALE!";
86  *   }
87  * }
88  *
89  * // Thread2: Exceptions are bad!
90  * void processResult() {
91  *   auto ep = globalExceptionWrapper.get();
92  *   if (!ep.with_exception<FacePlantException>([&](
93  *     FacePlantException& faceplant) {
94  *       LOG(ERROR) << "FACEPLANT";
95  *     })) {
96  *     ep.with_exception<FailWhaleException>([&](
97  *       FailWhaleException& failwhale) {
98  *         LOG(ERROR) << "FAILWHALE!";
99  *       });
100  *   }
101  * }
102  *
103  */
104 class exception_wrapper {
105  public:
106   exception_wrapper() : throwfn_(nullptr) { }
107
108   // Implicitly construct an exception_wrapper from any std::exception
109   template <typename T, typename =
110     typename std::enable_if<std::is_base_of<std::exception, T>::value>::type>
111   /* implicit */ exception_wrapper(T&& exn) {
112     item_ = std::make_shared<T>(std::forward<T>(exn));
113     throwfn_ = folly::detail::Thrower<T>::doThrow;
114   }
115
116   void throwException() const {
117     if (throwfn_) {
118       throwfn_(item_.get());
119     } else if (eptr_) {
120       std::rethrow_exception(eptr_);
121     }
122   }
123
124   explicit operator bool() const {
125     return item_ || eptr_;
126   }
127
128   // This implementation is similar to std::exception_ptr's implementation
129   // where two exception_wrappers are equal when the address in the underlying
130   // reference field both point to the same exception object.  The reference
131   // field remains the same when the exception_wrapper is copied or when
132   // the exception_wrapper is "rethrown".
133   bool operator==(const exception_wrapper& a) const {
134     if (item_) {
135       return a.item_ && item_.get() == a.item_.get();
136     } else {
137       return eptr_ == a.eptr_;
138     }
139   }
140
141   bool operator!=(const exception_wrapper& a) const {
142     return !(*this == a);
143   }
144
145   // This will return a non-nullptr only if the exception is held as a
146   // copy.  It is the only interface which will distinguish between an
147   // exception held this way, and by exception_ptr.  You probably
148   // shouldn't use it at all.
149   std::exception* getCopied() { return item_.get(); }
150   const std::exception* getCopied() const { return item_.get(); }
151
152   fbstring what() const {
153     if (item_) {
154       return exceptionStr(*item_);
155     } else if (eptr_) {
156       return estr_;
157     } else {
158       return fbstring();
159     }
160   }
161
162   fbstring class_name() const {
163     if (item_) {
164       return demangle(typeid(*item_));
165     } else if (eptr_) {
166       return ename_;
167     } else {
168       return fbstring();
169     }
170   }
171
172   template <class Ex>
173   bool is_compatible_with() const {
174     if (item_) {
175       return dynamic_cast<const Ex*>(item_.get());
176     } else if (eptr_) {
177       try {
178         std::rethrow_exception(eptr_);
179       } catch (std::exception& e) {
180         return dynamic_cast<const Ex*>(&e);
181       } catch (...) {
182         // fall through
183       }
184     }
185     return false;
186   }
187
188   template <class Ex, class F>
189   bool with_exception(F f) {
190     return with_exception1<Ex>(f, this);
191   }
192
193   template <class Ex, class F>
194   bool with_exception(F f) const {
195     return with_exception1<const Ex>(f, this);
196   }
197
198   std::exception_ptr getExceptionPtr() const {
199     if (eptr_) {
200       return eptr_;
201     }
202
203     try {
204       throwException();
205     } catch (...) {
206       return std::current_exception();
207     }
208     return std::exception_ptr();
209   }
210
211 protected:
212   // Optimized case: if we know what type the exception is, we can
213   // store a copy of the concrete type, and a helper function so we
214   // can rethrow it.
215   std::shared_ptr<std::exception> item_;
216   void (*throwfn_)(std::exception*);
217   // Fallback case: store the library wrapper, which is less efficient
218   // but gets the job done.  Also store exceptionPtr() the name of the
219   // exception type, so we can at least get those back out without
220   // having to rethrow.
221   std::exception_ptr eptr_;
222   std::string estr_;
223   std::string ename_;
224
225   template <class T, class... Args>
226   friend exception_wrapper make_exception_wrapper(Args&&... args);
227
228 private:
229   // What makes this useful is that T can be exception_wrapper* or
230   // const exception_wrapper*, and the compiler will use the
231   // instantiation which works with F.
232   template <class Ex, class F, class T>
233   static bool with_exception1(F f, T* that) {
234     if (that->item_) {
235       if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
236         f(*ex);
237         return true;
238       }
239     } else if (that->eptr_) {
240       try {
241         std::rethrow_exception(that->eptr_);
242       } catch (std::exception& e) {
243         if (auto ex = dynamic_cast<Ex*>(&e)) {
244           f(*ex);
245           return true;
246         }
247       } catch (...) {
248         // fall through
249       }
250     }
251     return false;
252   }
253 };
254
255 template <class T, class... Args>
256 exception_wrapper make_exception_wrapper(Args&&... args) {
257   exception_wrapper ew;
258   ew.item_ = std::make_shared<T>(std::forward<Args>(args)...);
259   ew.throwfn_ = folly::detail::Thrower<T>::doThrow;
260   return ew;
261 }
262
263 /*
264  * try_and_catch is a simple replacement for try {} catch(){} that allows you to
265  * specify which derived exceptions you would like to catch and store in an
266  * exception_wrapper.
267  *
268  * Because we cannot build an equivalent of std::current_exception(), we need
269  * to catch every derived exception that we are interested in catching.
270  *
271  * Exceptions should be listed in the reverse order that you would write your
272  * catch statements (that is, std::exception& should be first).
273  *
274  * NOTE: Although implemented as a derived class (for syntactic delight), don't
275  * be confused - you should not pass around try_and_catch objects!
276  *
277  * Example Usage:
278  *
279  * // This catches my runtime_error and if I call throwException() on ew, it
280  * // will throw a runtime_error
281  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
282  *   if (badThingHappens()) {
283  *     throw std::runtime_error("ZOMG!");
284  *   }
285  * });
286  *
287  * // This will catch the exception and if I call throwException() on ew, it
288  * // will throw a std::exception
289  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
290  *   if (badThingHappens()) {
291  *     throw std::exception();
292  *   }
293  * });
294  *
295  * // This will not catch the exception and it will be thrown.
296  * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
297  *   if (badThingHappens()) {
298  *     throw std::exception();
299  *   }
300  * });
301  */
302
303 template <typename... Exceptions>
304 class try_and_catch;
305
306 template <typename LastException, typename... Exceptions>
307 class try_and_catch<LastException, Exceptions...> :
308     public try_and_catch<Exceptions...> {
309  public:
310   template <typename F>
311   explicit try_and_catch(F&& fn) : Base() {
312     call_fn(fn);
313   }
314
315  protected:
316   typedef try_and_catch<Exceptions...> Base;
317
318   try_and_catch() : Base() {}
319
320   template <typename Ex>
321   void assign_eptr(Ex& e) {
322     this->eptr_ = std::current_exception();
323     this->estr_ = exceptionStr(e).toStdString();
324     this->ename_ = demangle(typeid(e)).toStdString();
325   }
326
327   template <typename Ex>
328   struct optimize {
329     static const bool value =
330       std::is_base_of<std::exception, Ex>::value &&
331       std::is_copy_assignable<Ex>::value &&
332       !std::is_abstract<Ex>::value;
333   };
334
335   template <typename Ex>
336   typename std::enable_if<!optimize<Ex>::value>::type
337   assign_exception(Ex& e) {
338     assign_eptr(e);
339   }
340
341   template <typename Ex>
342   typename std::enable_if<optimize<Ex>::value>::type
343   assign_exception(Ex& e) {
344     this->item_ = std::make_shared<Ex>(e);
345     this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
346   }
347
348   template <typename F>
349   void call_fn(F&& fn) {
350     try {
351       Base::call_fn(std::move(fn));
352     } catch (LastException& e) {
353       if (typeid(e) == typeid(LastException&)) {
354         assign_exception(e);
355       } else {
356         assign_eptr(e);
357       }
358     }
359   }
360 };
361
362 template<>
363 class try_and_catch<> : public exception_wrapper {
364  public:
365   try_and_catch() {}
366
367  protected:
368   template <typename F>
369   void call_fn(F&& fn) {
370     fn();
371   }
372 };
373 }
374 #endif