exception wrapper
authorMarc Celani <marccelani@fb.com>
Thu, 1 May 2014 17:44:13 +0000 (10:44 -0700)
committerDave Watson <davejwatson@fb.com>
Tue, 20 May 2014 19:53:57 +0000 (12:53 -0700)
Summary:
folly::exception_wrapper is a different take on std::exception_ptr to
suit a specific use case.

The good: std::exception_ptr is not templated, so it can easily be used in
different classes without template creep. You can pass errors around between
threads or simply between modules. Rethrowing the exception throws the *proper*
derived class exception, not some base class.

The bad: Getting access to the exception requires throwing, which is expensive.
Many users of popular frameworks that take advantage of std::exception_ptr
check if the exception is set, and if so do some error handling without actually
knowing the type of the exception or logging its message, just to avoid the cost
of rethrowing the exception.

The ugly: Creating an exception_ptr requires throwing the exception as least
once. This is bad in the performance sensitive case where users will not even
inspect the exception.

This class takes advantage of the good while avoiding the requirement to throw.
By using a templated deleter and thrower function, we can create an
exception_wrapper which is properly managed, can be thrown, and can be retrieved
as a void* with a get() function. Users that previously caught exceptions are
now able to dynamically cast to different exception types they formerly caught
to avoid the unwind cost while still getting details about the error.

Test Plan: unit test

Reviewed By: davejwatson@fb.com

FB internal diff: D1305470

@override-unit-failures

folly/ExceptionWrapper.h [new file with mode: 0644]
folly/Makefile.am
folly/detail/ExceptionWrapper.h [new file with mode: 0644]
folly/test/ExceptionWrapperBenchmark.cpp [new file with mode: 0644]
folly/test/ExceptionWrapperTest.cpp [new file with mode: 0644]

diff --git a/folly/ExceptionWrapper.h b/folly/ExceptionWrapper.h
new file mode 100644 (file)
index 0000000..647c870
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_EXCEPTIONWRAPPER_H
+#define FOLLY_EXCEPTIONWRAPPER_H
+
+#include <exception>
+#include <memory>
+#include "folly/detail/ExceptionWrapper.h"
+
+namespace folly {
+
+class exception_wrapper {
+ public:
+  exception_wrapper() : throwfn_(nullptr) { }
+
+  void throwException() {
+    if (throwfn_) {
+      throwfn_(item_.get());
+    }
+  }
+
+  std::exception* get() {
+    return item_.get();
+  }
+
+ private:
+  std::shared_ptr<std::exception> item_;
+  void (*throwfn_)(void*);
+
+  template <class T, class... Args>
+  friend exception_wrapper make_exception_wrapper(Args... args);
+};
+
+template <class T, class... Args>
+exception_wrapper make_exception_wrapper(Args... args) {
+  exception_wrapper ew;
+  ew.item_ = std::make_shared<T>(std::forward<Args>(args)...);
+  ew.throwfn_ = folly::detail::thrower<T>::doThrow;
+  return ew;
+}
+
+}
+#endif
index 235387c2489a73b466dd2b1a4f5d1e9c9196f42f..eac45836cf74f29922baed23ac8c2a40151e33a0 100644 (file)
@@ -42,6 +42,7 @@ nobase_follyinclude_HEADERS = \
        detail/ChecksumDetail.h \
        detail/Clock.h \
        detail/DiscriminatedPtrDetail.h \
+       detail/ExceptionWrapper.h \
        detail/FileUtilDetail.h \
        detail/FingerprintPolynomial.h \
        detail/FunctionalExcept.h \
@@ -65,6 +66,7 @@ nobase_follyinclude_HEADERS = \
        Fingerprint.h \
        folly-config.h \
        Exception.h \
+       ExceptionWrapper.h \
        Foreach.h \
        FormatArg.h \
        Format.h \
diff --git a/folly/detail/ExceptionWrapper.h b/folly/detail/ExceptionWrapper.h
new file mode 100644 (file)
index 0000000..bc88bf8
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_DETAIL_EXCEPTIONWRAPPER_H
+#define FOLLY_DETAIL_EXCEPTIONWRAPPER_H
+
+namespace folly { namespace detail {
+
+template <class T>
+class thrower {
+ public:
+  static void doThrow(void* obj) {
+    throw *((T*)(obj));
+  }
+};
+
+}}
+
+#endif
diff --git a/folly/test/ExceptionWrapperBenchmark.cpp b/folly/test/ExceptionWrapperBenchmark.cpp
new file mode 100644 (file)
index 0000000..31e28f6
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "folly/ExceptionWrapper.h"
+
+#include <gflags/gflags.h>
+#include <atomic>
+#include <exception>
+#include <vector>
+#include <stdexcept>
+#include <thread>
+
+#include "folly/Benchmark.h"
+
+DEFINE_int32(num_threads, 32, "Number of threads to run concurrency "
+                              "benchmarks");
+
+/*
+ * Use case 1: Library wraps errors in either exception_wrapper or
+ * exception_ptr, but user does not care what the exception is after learning
+ * that there is one.
+ */
+BENCHMARK(exception_ptr_create_and_test, iters) {
+  std::runtime_error e("payload");
+  for (int i = 0; i < iters; ++i) {
+    auto ep = std::make_exception_ptr(e);
+    assert(ep);
+  }
+}
+
+BENCHMARK_RELATIVE(exception_wrapper_create_and_test, iters) {
+  std::runtime_error e("payload");
+  for (int i = 0; i < iters; ++i) {
+    auto ew = folly::make_exception_wrapper<std::runtime_error>(e);
+    assert(ew.get());
+  }
+}
+
+BENCHMARK_DRAW_LINE()
+
+BENCHMARK(exception_ptr_create_and_test_concurrent, iters) {
+  std::atomic<bool> go(false);
+  std::vector<std::thread> threads;
+  BENCHMARK_SUSPEND {
+    for (int t = 0; t < FLAGS_num_threads; ++t) {
+      threads.emplace_back([&go, iters] {
+        while (!go) { }
+        std::runtime_error e("payload");
+        for (int i = 0; i < iters; ++i) {
+          auto ep = std::make_exception_ptr(e);
+          assert(ep);
+        }
+      });
+    }
+  }
+  go.store(true);
+  for (auto& t : threads) {
+    t.join();
+  }
+}
+
+BENCHMARK_RELATIVE(exception_wrapper_create_and_test_concurrent, iters) {
+  std::atomic<bool> go(false);
+  std::vector<std::thread> threads;
+  BENCHMARK_SUSPEND {
+    for (int t = 0; t < FLAGS_num_threads; ++t) {
+      threads.emplace_back([&go, iters] {
+        while (!go) { }
+        std::runtime_error e("payload");
+        for (int i = 0; i < iters; ++i) {
+          auto ew = folly::make_exception_wrapper<std::runtime_error>(e);
+          assert(ew.get());
+        }
+      });
+    }
+  }
+  go.store(true);
+  for (auto& t : threads) {
+    t.join();
+  }
+}
+
+BENCHMARK_DRAW_LINE()
+
+/*
+ * Use case 2: Library wraps errors in either exception_wrapper or
+ * exception_ptr, and user wants to handle std::runtime_error. This can be done
+ * either by rehtrowing or with dynamic_cast.
+ */
+BENCHMARK(exception_ptr_create_and_throw, iters) {
+  std::runtime_error e("payload");
+  for (int i = 0; i < iters; ++i) {
+    auto ep = std::make_exception_ptr(e);
+    try {
+      std::rethrow_exception(ep);
+      assert(false);
+    } catch (std::runtime_error& e) {
+    }
+  }
+}
+
+BENCHMARK_RELATIVE(exception_wrapper_create_and_throw, iters) {
+  std::runtime_error e("payload");
+  for (int i = 0; i < iters; ++i) {
+    auto ew = folly::make_exception_wrapper<std::runtime_error>(e);
+    try {
+      ew.throwException();
+      assert(false);
+    } catch (std::runtime_error& e) {
+    }
+  }
+}
+
+BENCHMARK_RELATIVE(exception_wrapper_create_and_cast, iters) {
+  std::runtime_error e("payload");
+  for (int i = 0; i < iters; ++i) {
+    auto ew = folly::make_exception_wrapper<std::runtime_error>(e);
+    std::exception* basePtr = static_cast<std::exception*>(ew.get());
+    auto ep = dynamic_cast<std::runtime_error*>(basePtr);
+    assert(ep);
+  }
+}
+
+
+BENCHMARK_DRAW_LINE()
+
+BENCHMARK(exception_ptr_create_and_throw_concurrent, iters) {
+  std::atomic<bool> go(false);
+  std::vector<std::thread> threads;
+  BENCHMARK_SUSPEND {
+    for (int t = 0; t < FLAGS_num_threads; ++t) {
+      threads.emplace_back([&go, iters] {
+        while (!go) { }
+        std::runtime_error e("payload");
+        for (int i = 0; i < iters; ++i) {
+          auto ep = std::make_exception_ptr(e);
+          try {
+            std::rethrow_exception(ep);
+            assert(false);
+          } catch (std::runtime_error& e) {
+          }
+        }
+      });
+    }
+  }
+  go.store(true);
+  for (auto& t : threads) {
+    t.join();
+  }
+}
+
+BENCHMARK_RELATIVE(exception_wrapper_create_and_throw_concurrent, iters) {
+  std::atomic<bool> go(false);
+  std::vector<std::thread> threads;
+  BENCHMARK_SUSPEND {
+    for (int t = 0; t < FLAGS_num_threads; ++t) {
+      threads.emplace_back([&go, iters] {
+        while (!go) { }
+        std::runtime_error e("payload");
+        for (int i = 0; i < iters; ++i) {
+          auto ew = folly::make_exception_wrapper<std::runtime_error>(e);
+          try {
+            ew.throwException();
+            assert(false);
+          } catch (std::runtime_error& e) {
+          }
+        }
+      });
+    }
+  }
+  go.store(true);
+  for (auto& t : threads) {
+    t.join();
+  }
+}
+
+BENCHMARK_RELATIVE(exception_wrapper_create_and_cast_concurrent, iters) {
+  std::atomic<bool> go(false);
+  std::vector<std::thread> threads;
+  BENCHMARK_SUSPEND {
+    for (int t = 0; t < FLAGS_num_threads; ++t) {
+      threads.emplace_back([&go, iters] {
+        while (!go) { }
+        std::runtime_error e("payload");
+        for (int i = 0; i < iters; ++i) {
+          auto ew = folly::make_exception_wrapper<std::runtime_error>(e);
+          std::exception* basePtr = static_cast<std::exception*>(ew.get());
+          auto ep = dynamic_cast<std::runtime_error*>(basePtr);
+          assert(ep);
+        }
+      });
+    }
+  }
+  go.store(true);
+  for (auto& t : threads) {
+    t.join();
+  }
+}
+
+int main(int argc, char *argv[]) {
+  google::ParseCommandLineFlags(&argc, &argv, true);
+  folly::runBenchmarks();
+  return 0;
+}
+
+/*
+_bin/folly/test/exception_wrapper_benchmark --bm_min_iters=100000
+============================================================================
+folly/test/ExceptionWrapperBenchmark.cpp        relative  time/iter  iters/s
+============================================================================
+exception_ptr_create_and_test                                2.03us  492.88K
+exception_wrapper_create_and_test               2542.59%    79.80ns   12.53M
+----------------------------------------------------------------------------
+exception_ptr_create_and_test_concurrent                   162.39us    6.16K
+exception_wrapper_create_and_test_concurrent    95847.91%   169.43ns    5.90M
+----------------------------------------------------------------------------
+exception_ptr_create_and_throw                               4.24us  236.06K
+exception_wrapper_create_and_throw               141.15%     3.00us  333.20K
+exception_wrapper_create_and_cast               5321.54%    79.61ns   12.56M
+----------------------------------------------------------------------------
+exception_ptr_create_and_throw_concurrent                  330.88us    3.02K
+exception_wrapper_create_and_throw_concurrent    143.66%   230.32us    4.34K
+exception_wrapper_create_and_cast_concurrent    194828.54%   169.83ns    5.89M
+============================================================================
+*/
diff --git a/folly/test/ExceptionWrapperTest.cpp b/folly/test/ExceptionWrapperTest.cpp
new file mode 100644 (file)
index 0000000..6aad3f1
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <stdexcept>
+#include "folly/ExceptionWrapper.h"
+
+using namespace folly;
+
+// Tests that when we call throwException, the proper type is thrown (derived)
+TEST(ExceptionWrapper, throw_test) {
+  std::runtime_error e("payload");
+  auto ew = make_exception_wrapper<std::runtime_error>(e);
+
+  std::vector<exception_wrapper> container;
+  container.push_back(ew);
+
+  try {
+    container[0].throwException();
+  } catch (std::runtime_error& e) {
+    std::string expected = "payload";
+    std::string actual = e.what();
+    EXPECT_EQ(expected, actual);
+  }
+}