New C++17 backport: folly::enable_shared_from_this
[folly.git] / folly / test / SpinLockTest.cpp
index 323050e70e46d2840ff3c02639a5f33bea938896..7c263c377ed55ae2e0ee83723faa9aaa6a1ae9c1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 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.
  */
 #include <folly/SpinLock.h>
 
-#include <gtest/gtest.h>
+#include <folly/Random.h>
+
 #include <thread>
 
-using folly::SpinLock;
-using folly::SpinLockGuard;
+#include <folly/portability/Asm.h>
+#include <folly/portability/GTest.h>
+
+using folly::SpinLockGuardImpl;
 
 namespace {
 
+template <typename LOCK>
 struct LockedVal {
   int ar[1024];
-  SpinLock lock;
+  LOCK lock;
 
   LockedVal() {
     memset(ar, 0, sizeof ar);
   }
 };
 
-LockedVal v;
-void splock_test() {
+template <typename LOCK>
+void spinlockTestThread(LockedVal<LOCK>* v) {
   const int max = 1000;
-  unsigned int seed = (uintptr_t)pthread_self();
+  auto rng = folly::ThreadLocalPRNG();
   for (int i = 0; i < max; i++) {
-    asm("pause");
-    SpinLockGuard g(v.lock);
+    folly::asm_pause();
+    SpinLockGuardImpl<LOCK> g(v->lock);
 
-    int first = v.ar[0];
-    for (size_t i = 1; i < sizeof v.ar / sizeof i; ++i) {
-      EXPECT_EQ(first, v.ar[i]);
+    int first = v->ar[0];
+    for (size_t j = 1; j < sizeof v->ar / sizeof j; ++j) {
+      EXPECT_EQ(first, v->ar[j]);
     }
 
-    int byte = rand_r(&seed);
-    memset(v.ar, char(byte), sizeof v.ar);
+    int byte = folly::Random::rand32(rng);
+    memset(v->ar, char(byte), sizeof v->ar);
   }
 }
 
+template <typename LOCK>
 struct TryLockState {
-  SpinLock lock1;
-  SpinLock lock2;
+  LOCK lock1;
+  LOCK lock2;
   bool locked{false};
   uint64_t obtained{0};
   uint64_t failed{0};
 };
-TryLockState tryState;
 
-void trylock_test() {
-  const int max = 1000;
+template <typename LOCK>
+void trylockTestThread(TryLockState<LOCK>* state, size_t count) {
   while (true) {
-    asm("pause");
-    SpinLockGuard g(tryState.lock1);
-    if (tryState.obtained >= max) {
+    folly::asm_pause();
+    SpinLockGuardImpl<LOCK> g(state->lock1);
+    if (state->obtained >= count) {
       break;
     }
 
-    bool ret = tryState.lock2.trylock();
-    EXPECT_NE(tryState.locked, ret);
+    bool ret = state->lock2.trylock();
+    EXPECT_NE(state->locked, ret);
 
     if (ret) {
       // We got lock2.
-      ++tryState.obtained;
-      tryState.locked = true;
-
-      // Release lock1 and let other threads try to obtain lock2
-      tryState.lock1.unlock();
-      asm("pause");
-      tryState.lock1.lock();
-
-      tryState.locked = false;
-      tryState.lock2.unlock();
+      ++state->obtained;
+      state->locked = true;
+
+      // Release lock1 and wait until at least one other thread fails to
+      // obtain the lock2 before continuing.
+      auto oldFailed = state->failed;
+      while (state->failed == oldFailed && state->obtained < count) {
+        state->lock1.unlock();
+        folly::asm_pause();
+        state->lock1.lock();
+      }
+
+      state->locked = false;
+      state->lock2.unlock();
     } else {
-      ++tryState.failed;
+      ++state->failed;
     }
   }
 }
 
-} // unnamed namespace
-
-TEST(SpinLock, Correctness) {
+template <typename LOCK>
+void correctnessTest() {
   int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2;
   std::vector<std::thread> threads;
+  LockedVal<LOCK> v;
   for (int i = 0; i < nthrs; ++i) {
-    threads.push_back(std::thread(splock_test));
+    threads.push_back(std::thread(spinlockTestThread<LOCK>, &v));
   }
   for (auto& t : threads) {
     t.join();
   }
 }
 
-TEST(SpinLock, TryLock) {
-  int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2;
+template <typename LOCK>
+void trylockTest() {
+  int nthrs = sysconf(_SC_NPROCESSORS_ONLN) + 4;
   std::vector<std::thread> threads;
+  TryLockState<LOCK> state;
+  size_t count = 100;
   for (int i = 0; i < nthrs; ++i) {
-    threads.push_back(std::thread(trylock_test));
+    threads.push_back(std::thread(trylockTestThread<LOCK>, &state, count));
   }
   for (auto& t : threads) {
     t.join();
   }
 
-  EXPECT_EQ(1000, tryState.obtained);
-  EXPECT_GT(tryState.failed, 0);
-  LOG(INFO) << "failed count: " << tryState.failed;
+  EXPECT_EQ(count, state.obtained);
+  // Each time the code obtains lock2 it waits for another thread to fail
+  // to acquire it.  The only time this might not happen is on the very last
+  // loop when no other threads are left.
+  EXPECT_GE(state.failed + 1, state.obtained);
+}
+
+} // unnamed namespace
+
+#if __x86_64__
+TEST(SpinLock, MslCorrectness) {
+  correctnessTest<folly::SpinLockMslImpl>();
+}
+TEST(SpinLock, MslTryLock) {
+  trylockTest<folly::SpinLockMslImpl>();
+}
+#endif
+
+#if __APPLE__
+TEST(SpinLock, AppleCorrectness) {
+  correctnessTest<folly::SpinLockAppleImpl>();
+}
+TEST(SpinLock, AppleTryLock) {
+  trylockTest<folly::SpinLockAppleImpl>();
+}
+#endif
+
+#if FOLLY_HAVE_PTHREAD_SPINLOCK_T
+TEST(SpinLock, PthreadCorrectness) {
+  correctnessTest<folly::SpinLockPthreadImpl>();
+}
+TEST(SpinLock, PthreadTryLock) {
+  trylockTest<folly::SpinLockPthreadImpl>();
+}
+#endif
+
+TEST(SpinLock, MutexCorrectness) {
+  correctnessTest<folly::SpinLockPthreadMutexImpl>();
+}
+TEST(SpinLock, MutexTryLock) {
+  trylockTest<folly::SpinLockPthreadMutexImpl>();
 }