Delete | operator for Subprocess::Options
[folly.git] / folly / test / ThreadLocalTest.cpp
index 1c948257f35e360a593d888dafc974fc94fcbc03..c33549d0cb253856517076ae59726d1f8facaf12 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#include "folly/ThreadLocal.h"
+#include <folly/ThreadLocal.h>
 
-#include <sys/types.h>
+#ifndef _WIN32
+#include <dlfcn.h>
 #include <sys/wait.h>
-#include <map>
-#include <unordered_map>
-#include <set>
+#endif
+
+#include <sys/types.h>
+
+#include <array>
 #include <atomic>
-#include <mutex>
+#include <chrono>
 #include <condition_variable>
+#include <limits.h>
+#include <map>
+#include <mutex>
+#include <set>
 #include <thread>
-#include <unistd.h>
-#include <boost/thread/tss.hpp>
-#include <gtest/gtest.h>
-#include <gflags/gflags.h>
+#include <unordered_map>
+
 #include <glog/logging.h>
-#include "folly/Benchmark.h"
+
+#include <folly/Baton.h>
+#include <folly/Memory.h>
+#include <folly/ThreadId.h>
+#include <folly/experimental/io/FsUtil.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/Unistd.h>
 
 using namespace folly;
 
@@ -42,7 +53,7 @@ struct Widget {
   }
 
   static void customDeleter(Widget* w, TLPDestructionMode mode) {
-    totalVal_ += (mode == TLPDestructionMode::ALL_THREADS) * 1000;
+    totalVal_ += (mode == TLPDestructionMode::ALL_THREADS) ? 1000 : 1;
     delete w;
   }
 };
@@ -66,6 +77,37 @@ TEST(ThreadLocalPtr, CustomDeleter1) {
         w.reset(new Widget(), Widget::customDeleter);
         w.get()->val_ += 10;
       }).join();
+    EXPECT_EQ(11, Widget::totalVal_);
+  }
+  EXPECT_EQ(11, Widget::totalVal_);
+}
+
+TEST(ThreadLocalPtr, CustomDeleterOwnershipTransfer) {
+  Widget::totalVal_ = 0;
+  {
+    ThreadLocalPtr<Widget> w;
+    auto deleter = [](Widget* ptr) {
+      Widget::customDeleter(ptr, TLPDestructionMode::THIS_THREAD);
+    };
+    std::unique_ptr<Widget, decltype(deleter)> source(new Widget(), deleter);
+    std::thread([&w, &source]() {
+      w.reset(std::move(source));
+      w.get()->val_ += 10;
+    }).join();
+    EXPECT_EQ(11, Widget::totalVal_);
+  }
+  EXPECT_EQ(11, Widget::totalVal_);
+}
+
+TEST(ThreadLocalPtr, DefaultDeleterOwnershipTransfer) {
+  Widget::totalVal_ = 0;
+  {
+    ThreadLocalPtr<Widget> w;
+    auto source = folly::make_unique<Widget>();
+    std::thread([&w, &source]() {
+      w.reset(std::move(source));
+      w.get()->val_ += 10;
+    }).join();
     EXPECT_EQ(10, Widget::totalVal_);
   }
   EXPECT_EQ(10, Widget::totalVal_);
@@ -81,6 +123,39 @@ TEST(ThreadLocalPtr, resetNull) {
   EXPECT_FALSE(tl);
 }
 
+TEST(ThreadLocalPtr, TestRelease) {
+  Widget::totalVal_ = 0;
+  ThreadLocalPtr<Widget> w;
+  std::unique_ptr<Widget> wPtr;
+  std::thread([&w, &wPtr]() {
+      w.reset(new Widget());
+      w.get()->val_ += 10;
+
+      wPtr.reset(w.release());
+    }).join();
+  EXPECT_EQ(0, Widget::totalVal_);
+  wPtr.reset();
+  EXPECT_EQ(10, Widget::totalVal_);
+}
+
+TEST(ThreadLocalPtr, CreateOnThreadExit) {
+  Widget::totalVal_ = 0;
+  ThreadLocal<Widget> w;
+  ThreadLocalPtr<int> tl;
+
+  std::thread([&] {
+    tl.reset(new int(1),
+             [&](int* ptr, TLPDestructionMode /* mode */) {
+               delete ptr;
+               // This test ensures Widgets allocated here are not leaked.
+               ++w.get()->val_;
+               ThreadLocal<Widget> wl;
+               ++wl.get()->val_;
+             });
+  }).join();
+  EXPECT_EQ(2, Widget::totalVal_);
+}
+
 // Test deleting the ThreadLocalPtr object
 TEST(ThreadLocalPtr, CustomDeleter2) {
   Widget::totalVal_ = 0;
@@ -164,7 +239,7 @@ TEST(ThreadLocal, SimpleRepeatDestructor) {
 
 TEST(ThreadLocal, InterleavedDestructors) {
   Widget::totalVal_ = 0;
-  ThreadLocal<Widget>* w = NULL;
+  std::unique_ptr<ThreadLocal<Widget>> w;
   int wVersion = 0;
   const int wVersionMax = 2;
   int thIter = 0;
@@ -194,8 +269,7 @@ TEST(ThreadLocal, InterleavedDestructors) {
     {
       std::lock_guard<std::mutex> g(lock);
       thIterPrev = thIter;
-      delete w;
-      w = new ThreadLocal<Widget>();
+      w.reset(new ThreadLocal<Widget>());
       ++wVersion;
     }
     while (true) {
@@ -298,6 +372,80 @@ TEST(ThreadLocal, Movable2) {
   EXPECT_EQ(4, tls.size());
 }
 
+namespace {
+
+constexpr size_t kFillObjectSize = 300;
+
+std::atomic<uint64_t> gDestroyed;
+
+/**
+ * Fill a chunk of memory with a unique-ish pattern that includes the thread id
+ * (so deleting one of these from another thread would cause a failure)
+ *
+ * Verify it explicitly and on destruction.
+ */
+class FillObject {
+ public:
+  explicit FillObject(uint64_t idx) : idx_(idx) {
+    uint64_t v = val();
+    for (size_t i = 0; i < kFillObjectSize; ++i) {
+      data_[i] = v;
+    }
+  }
+
+  void check() {
+    uint64_t v = val();
+    for (size_t i = 0; i < kFillObjectSize; ++i) {
+      CHECK_EQ(v, data_[i]);
+    }
+  }
+
+  ~FillObject() {
+    ++gDestroyed;
+  }
+
+ private:
+  uint64_t val() const {
+    return (idx_ << 40) | folly::getCurrentThreadID();
+  }
+
+  uint64_t idx_;
+  uint64_t data_[kFillObjectSize];
+};
+
+}  // namespace
+
+TEST(ThreadLocal, Stress) {
+  static constexpr size_t numFillObjects = 250;
+  std::array<ThreadLocalPtr<FillObject>, numFillObjects> objects;
+
+  static constexpr size_t numThreads = 32;
+  static constexpr size_t numReps = 20;
+
+  std::vector<std::thread> threads;
+  threads.reserve(numThreads);
+
+  for (size_t k = 0; k < numThreads; ++k) {
+    threads.emplace_back([&objects] {
+      for (size_t rep = 0; rep < numReps; ++rep) {
+        for (size_t i = 0; i < objects.size(); ++i) {
+          objects[i].reset(new FillObject(rep * objects.size() + i));
+          std::this_thread::sleep_for(std::chrono::microseconds(100));
+        }
+        for (size_t i = 0; i < objects.size(); ++i) {
+          objects[i]->check();
+        }
+      }
+    });
+  }
+
+  for (auto& t : threads) {
+    t.join();
+  }
+
+  EXPECT_EQ(numFillObjects * numThreads * numReps, gDestroyed);
+}
+
 // Yes, threads and fork don't mix
 // (http://cppwisdom.quora.com/Why-threads-and-fork-dont-mix) but if you're
 // stupid or desperate enough to try, we shouldn't stand in your way.
@@ -325,6 +473,7 @@ int totalValue() {
 
 }  // namespace
 
+#ifdef FOLLY_HAVE_PTHREAD_ATFORK
 TEST(ThreadLocal, Fork) {
   EXPECT_EQ(1, ptr->value());  // ensure created
   EXPECT_EQ(1, totalValue());
@@ -394,76 +543,97 @@ TEST(ThreadLocal, Fork) {
 
   EXPECT_EQ(1, totalValue());
 }
+#endif
 
-// Simple reference implementation using pthread_get_specific
-template<typename T>
-class PThreadGetSpecific {
- public:
-  PThreadGetSpecific() : key_(0) {
-    pthread_key_create(&key_, OnThreadExit);
-  }
+#ifndef _WIN32
+struct HoldsOneTag2 {};
 
-  T* get() const {
-    return static_cast<T*>(pthread_getspecific(key_));
-  }
-
-  void reset(T* t) {
-    delete get();
-    pthread_setspecific(key_, t);
+TEST(ThreadLocal, Fork2) {
+  // A thread-local tag that was used in the parent from a *different* thread
+  // (but not the forking thread) would cause the child to hang in a
+  // ThreadLocalPtr's object destructor. Yeah.
+  ThreadLocal<HoldsOne, HoldsOneTag2> p;
+  {
+    // use tag in different thread
+    std::thread t([&p] { p.get(); });
+    t.join();
   }
-  static void OnThreadExit(void* obj) {
-    delete static_cast<T*>(obj);
+  pid_t pid = fork();
+  if (pid == 0) {
+    {
+      ThreadLocal<HoldsOne, HoldsOneTag2> q;
+      q.get();
+    }
+    _exit(0);
+  } else if (pid > 0) {
+    int status;
+    EXPECT_EQ(pid, waitpid(pid, &status, 0));
+    EXPECT_TRUE(WIFEXITED(status));
+    EXPECT_EQ(0, WEXITSTATUS(status));
+  } else {
+    EXPECT_TRUE(false) << "fork failed";
   }
- private:
-  pthread_key_t key_;
-};
+}
 
-DEFINE_int32(numThreads, 8, "Number simultaneous threads for benchmarks.");
-
-#define REG(var)                                                \
-  BENCHMARK(FB_CONCATENATE(BM_mt_, var), iters) {               \
-    const int itersPerThread = iters / FLAGS_numThreads;        \
-    std::vector<std::thread> threads;                           \
-    for (int i = 0; i < FLAGS_numThreads; ++i) {                \
-      threads.push_back(std::thread([&]() {                     \
-        var.reset(new int(0));                                  \
-        for (int i = 0; i < itersPerThread; ++i) {              \
-          ++(*var.get());                                       \
-        }                                                       \
-      }));                                                      \
-    }                                                           \
-    for (auto& t : threads) {                                   \
-      t.join();                                                 \
-    }                                                           \
-  }
+// Elide this test when using any sanitizer. Otherwise, the dlopen'ed code
+// would end up running without e.g., ASAN-initialized data structures and
+// failing right away.
+#if !defined FOLLY_SANITIZE_ADDRESS && !defined UNDEFINED_SANITIZER && \
+    !defined FOLLY_SANITIZE_THREAD
 
-ThreadLocalPtr<int> tlp;
-REG(tlp);
-PThreadGetSpecific<int> pthread_get_specific;
-REG(pthread_get_specific);
-boost::thread_specific_ptr<int> boost_tsp;
-REG(boost_tsp);
-BENCHMARK_DRAW_LINE();
-
-int main(int argc, char** argv) {
-  testing::InitGoogleTest(&argc, argv);
-  google::ParseCommandLineFlags(&argc, &argv, true);
-  google::SetCommandLineOptionWithMode(
-    "bm_max_iters", "100000000", google::SET_FLAG_IF_DEFAULT
-  );
-  if (FLAGS_benchmark) {
-    folly::runBenchmarks();
-  }
-  return RUN_ALL_TESTS();
+TEST(ThreadLocal, SharedLibrary) {
+  auto exe = fs::executable_path();
+  auto lib = exe.parent_path() / "thread_local_test_lib.so";
+  auto handle = dlopen(lib.string().c_str(), RTLD_LAZY);
+  EXPECT_NE(nullptr, handle);
+
+  typedef void (*useA_t)();
+  dlerror();
+  useA_t useA = (useA_t) dlsym(handle, "useA");
+
+  const char *dlsym_error = dlerror();
+  EXPECT_EQ(nullptr, dlsym_error);
+
+  useA();
+
+  folly::Baton<> b11, b12, b21, b22;
+
+  std::thread t1([&]() {
+      useA();
+      b11.post();
+      b12.wait();
+    });
+
+  std::thread t2([&]() {
+      useA();
+      b21.post();
+      b22.wait();
+    });
+
+  b11.wait();
+  b21.wait();
+
+  dlclose(handle);
+
+  b12.post();
+  b22.post();
+
+  t1.join();
+  t2.join();
 }
 
-/*
-Ran with 24 threads on dual 12-core Xeon(R) X5650 @ 2.67GHz with 12-MB caches
-
-Benchmark                               Iters   Total t    t/iter iter/sec
-------------------------------------------------------------------------------
-*       BM_mt_tlp                   100000000  39.88 ms  398.8 ps  2.335 G
- +5.91% BM_mt_pthread_get_specific  100000000  42.23 ms  422.3 ps  2.205 G
- + 295% BM_mt_boost_tsp             100000000  157.8 ms  1.578 ns  604.5 M
-------------------------------------------------------------------------------
-*/
+#endif
+#endif
+
+namespace folly { namespace threadlocal_detail {
+struct PthreadKeyUnregisterTester {
+  PthreadKeyUnregister p;
+  constexpr PthreadKeyUnregisterTester() = default;
+};
+}}
+
+TEST(ThreadLocal, UnregisterClassHasConstExprCtor) {
+  folly::threadlocal_detail::PthreadKeyUnregisterTester x;
+  // yep!
+  SUCCEED();
+}