Make it work more generically
[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   void throwException() const {
109     if (throwfn_) {
110       throwfn_(item_.get());
111     } else if (eptr_) {
112       std::rethrow_exception(eptr_);
113     }
114   }
115
116   explicit operator bool() const {
117     return item_ || eptr_;
118   }
119
120   // This will return a non-nullptr only if the exception is held as a
121   // copy.  It is the only interface which will distinguish between an
122   // exception held this way, and by exception_ptr.  You probably
123   // shouldn't use it at all.
124   std::exception* getCopied() { return item_.get(); }
125   const std::exception* getCopied() const { return item_.get(); }
126
127   fbstring what() const {
128     if (item_) {
129       return exceptionStr(*item_.get());
130     } else if (eptr_) {
131       return estr_;
132     } else {
133       return fbstring();
134     }
135   }
136
137   template <class Ex>
138   bool is_compatible_with() const {
139     if (item_) {
140       return dynamic_cast<const Ex*>(getCopied());
141     } else if (eptr_) {
142       try {
143         std::rethrow_exception(eptr_);
144       } catch (std::exception& e) {
145         return dynamic_cast<const Ex*>(&e);
146       } catch (...) {
147         // fall through
148       }
149     }
150     return false;
151   }
152
153   template <class Ex, class F>
154   bool with_exception(F f) {
155     if (item_) {
156       if (auto ex = dynamic_cast<Ex*>(getCopied())) {
157         f(*ex);
158         return true;
159       }
160     } else if (eptr_) {
161       try {
162         std::rethrow_exception(eptr_);
163       } catch (std::exception& e) {
164         if (auto ex = dynamic_cast<Ex*>(&e)) {
165           f(*ex);
166           return true;
167         }
168       } catch (...) {
169         // fall through
170       }
171     }
172     return false;
173   }
174
175   template <class Ex, class F>
176   bool with_exception(F f) const {
177     return with_exception<const Ex>(f);
178   }
179
180   std::exception_ptr getExceptionPtr() const {
181     if (eptr_) {
182       return eptr_;
183     }
184
185     try {
186       throwException();
187     } catch (...) {
188       return std::current_exception();
189     }
190     return std::exception_ptr();
191   }
192
193  protected:
194   // Optimized case: if we know what type the exception is, we can
195   // store a copy of the concrete type, and a helper function so we
196   // can rethrow it.
197   std::shared_ptr<std::exception> item_;
198   void (*throwfn_)(std::exception*);
199   // Fallback case: store the library wrapper, which is less efficient
200   // but gets the job done.  Also store the the what() string, so we
201   // can at least get it back out without having to rethrow.
202   std::exception_ptr eptr_;
203   std::string estr_;
204
205   template <class T, class... Args>
206   friend exception_wrapper make_exception_wrapper(Args&&... args);
207 };
208
209 template <class T, class... Args>
210 exception_wrapper make_exception_wrapper(Args&&... args) {
211   exception_wrapper ew;
212   ew.item_ = std::make_shared<T>(std::forward<Args>(args)...);
213   ew.throwfn_ = folly::detail::Thrower<T>::doThrow;
214   return ew;
215 }
216
217 /*
218  * try_and_catch is a simple replacement for try {} catch(){} that allows you to
219  * specify which derived exceptions you would like to catch and store in an
220  * exception_wrapper.
221  *
222  * Because we cannot build an equivalent of std::current_exception(), we need
223  * to catch every derived exception that we are interested in catching.
224  *
225  * Exceptions should be listed in the reverse order that you would write your
226  * catch statements (that is, std::exception& should be first).
227  *
228  * NOTE: Although implemented as a derived class (for syntactic delight), don't
229  * be confused - you should not pass around try_and_catch objects!
230  *
231  * Example Usage:
232  *
233  * // This catches my runtime_error and if I call throwException() on ew, it
234  * // will throw a runtime_error
235  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
236  *   if (badThingHappens()) {
237  *     throw std::runtime_error("ZOMG!");
238  *   }
239  * });
240  *
241  * // This will catch the exception and if I call throwException() on ew, it
242  * // will throw a std::exception
243  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
244  *   if (badThingHappens()) {
245  *     throw std::exception();
246  *   }
247  * });
248  *
249  * // This will not catch the exception and it will be thrown.
250  * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
251  *   if (badThingHappens()) {
252  *     throw std::exception();
253  *   }
254  * });
255  */
256
257 template <typename... Exceptions>
258 class try_and_catch;
259
260 template <typename LastException, typename... Exceptions>
261 class try_and_catch<LastException, Exceptions...> :
262     public try_and_catch<Exceptions...> {
263  public:
264   template <typename F>
265   explicit try_and_catch(F&& fn) : Base() {
266     call_fn(fn);
267   }
268
269  protected:
270   typedef try_and_catch<Exceptions...> Base;
271
272   try_and_catch() : Base() {}
273
274   template <typename Ex>
275   typename std::enable_if<std::is_base_of<std::exception, Ex>::value>::type
276   assign_eptr(Ex& e) {
277     this->eptr_ = std::current_exception();
278     // The cast is needed so we get the desired overload of exceptionStr()
279     this->estr_ = exceptionStr(static_cast<std::exception&>(e)).toStdString();
280   }
281
282   template <typename Ex>
283   typename std::enable_if<!std::is_base_of<std::exception, Ex>::value>::type
284   assign_eptr(Ex& e) {
285     this->eptr_ = std::current_exception();
286     this->estr_ = exceptionStr(e).toStdString();
287   }
288
289   template <typename Ex>
290   struct optimize {
291     static const bool value =
292       std::is_base_of<std::exception, Ex>::value &&
293       std::is_copy_assignable<Ex>::value &&
294       !std::is_abstract<Ex>::value;
295   };
296
297   template <typename Ex>
298   typename std::enable_if<!optimize<Ex>::value>::type
299   assign_exception(Ex& e) {
300     assign_eptr(e);
301   }
302
303   template <typename Ex>
304   typename std::enable_if<optimize<Ex>::value>::type
305   assign_exception(Ex& e) {
306     this->item_ = std::make_shared<Ex>(e);
307     this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
308   }
309
310   template <typename F>
311   void call_fn(F&& fn) {
312     try {
313       Base::call_fn(std::move(fn));
314     } catch (LastException& e) {
315       if (typeid(e) == typeid(LastException&)) {
316         assign_exception(e);
317       } else {
318         assign_eptr(e);
319       }
320     }
321   }
322 };
323
324 template<>
325 class try_and_catch<> : public exception_wrapper {
326  public:
327   try_and_catch() {}
328
329  protected:
330   template <typename F>
331   void call_fn(F&& fn) {
332     fn();
333   }
334 };
335 }
336 #endif