Fix SimpleBarrier
[folly.git] / folly / test / SmallLocksTest.cpp
index e7cc04fc1e9c6deb7d0d36410c3596a3cbb98c1d..5bb9ddb59a06bb54c7d9b5f5a8c655da582b5c01 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2016 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/SmallLocks.h"
+#include <folly/SmallLocks.h>
+
+#include <folly/Random.h>
+
 #include <cassert>
 #include <cstdio>
+#include <mutex>
+#include <condition_variable>
 #include <string>
 #include <vector>
 #include <pthread.h>
-#include <unistd.h>
 
 #include <thread>
 
-#include <gtest/gtest.h>
+#include <folly/portability/Asm.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/Unistd.h>
 
-using std::string;
+using folly::MSLGuard;
+using folly::MicroLock;
 using folly::MicroSpinLock;
+using std::string;
+
+#ifdef FOLLY_PICO_SPIN_LOCK_H_
 using folly::PicoSpinLock;
-using folly::MSLGuard;
+#endif
 
 namespace {
 
@@ -45,31 +55,36 @@ struct LockedVal {
 
 // Compile time test for packed struct support (requires that both of
 // these classes are POD).
-struct ignore1 { MicroSpinLock msl; int16_t foo; } __attribute__((packed));
-struct ignore2 { PicoSpinLock<uint32_t> psl; int16_t foo; }
-  __attribute__((packed));
+FOLLY_PACK_PUSH
+struct ignore1 { MicroSpinLock msl; int16_t foo; } FOLLY_PACK_ATTR;
 static_assert(sizeof(ignore1) == 3, "Size check failed");
+static_assert(sizeof(MicroSpinLock) == 1, "Size check failed");
+#ifdef FOLLY_PICO_SPIN_LOCK_H_
+struct ignore2 { PicoSpinLock<uint32_t> psl; int16_t foo; } FOLLY_PACK_ATTR;
 static_assert(sizeof(ignore2) == 6, "Size check failed");
+#endif
+FOLLY_PACK_POP
 
 LockedVal v;
 void splock_test() {
 
   const int max = 1000;
-  unsigned int seed = (uintptr_t)pthread_self();
+  auto rng = folly::ThreadLocalPRNG();
   for (int i = 0; i < max; i++) {
-    asm("pause");
+    folly::asm_pause();
     MSLGuard g(v.lock);
 
     int first = v.ar[0];
-    for (int i = 1; i < sizeof v.ar / sizeof i; ++i) {
-      EXPECT_EQ(first, v.ar[i]);
+    for (size_t j = 1; j < sizeof v.ar / sizeof j; ++j) {
+      EXPECT_EQ(first, v.ar[j]);
     }
 
-    int byte = rand_r(&seed);
+    int byte = folly::Random::rand32(rng);
     memset(v.ar, char(byte), sizeof v.ar);
   }
 }
 
+#ifdef FOLLY_PICO_SPIN_LOCK_H_
 template<class T> struct PslTest {
   PicoSpinLock<T> lock;
 
@@ -81,7 +96,7 @@ template<class T> struct PslTest {
       std::lock_guard<PicoSpinLock<T>> guard(lock);
       lock.setData(ourVal);
       for (int n = 0; n < 10; ++n) {
-        asm volatile("pause");
+        folly::asm_volatile_pause();
         EXPECT_EQ(lock.getData(), ourVal);
       }
     }
@@ -101,6 +116,23 @@ void doPslTest() {
     t.join();
   }
 }
+#endif
+
+struct TestClobber {
+  TestClobber() {
+    lock_.init();
+  }
+
+  void go() {
+    std::lock_guard<MicroSpinLock> g(lock_);
+    // This bug depends on gcc register allocation and is very sensitive. We
+    // have to use DCHECK instead of EXPECT_*.
+    DCHECK(!lock_.try_lock());
+  }
+
+ private:
+  MicroSpinLock lock_;
+};
 
 }
 
@@ -117,6 +149,7 @@ TEST(SmallLocks, SpinLockCorrectness) {
   }
 }
 
+#ifdef FOLLY_PICO_SPIN_LOCK_H_
 TEST(SmallLocks, PicoSpinCorrectness) {
   doPslTest<int16_t>();
   doPslTest<uint16_t>();
@@ -140,3 +173,128 @@ TEST(SmallLocks, PicoSpinSigned) {
   }
   EXPECT_EQ(val.getData(), -8);
 }
+#endif
+
+TEST(SmallLocks, RegClobber) {
+  TestClobber().go();
+}
+
+FOLLY_PACK_PUSH
+#if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && \
+    (defined(__GNUC__) || defined(__GNUG__))
+static_assert(sizeof(MicroLock) == 4, "Size check failed");
+#else
+static_assert(sizeof(MicroLock) == 1, "Size check failed");
+#endif
+FOLLY_PACK_POP
+
+namespace {
+
+struct SimpleBarrier {
+
+  SimpleBarrier() : lock_(), cv_(), ready_(false) {}
+
+  void wait() {
+    std::unique_lock<std::mutex> lockHeld(lock_);
+    while (!ready_) {
+      cv_.wait(lockHeld);
+    }
+  }
+
+  void run() {
+    {
+      std::unique_lock<std::mutex> lockHeld(lock_);
+      ready_ = true;
+    }
+
+    cv_.notify_all();
+  }
+
+ private:
+  std::mutex lock_;
+  std::condition_variable cv_;
+  bool ready_;
+};
+}
+
+TEST(SmallLocks, MicroLock) {
+  volatile uint64_t counters[4] = {0, 0, 0, 0};
+  std::vector<std::thread> threads;
+  static const unsigned nrThreads = 20;
+  static const unsigned iterPerThread = 10000;
+  SimpleBarrier startBarrier;
+
+  assert(iterPerThread % 4 == 0);
+
+  // Embed the lock in a larger structure to ensure that we do not
+  // affect bits outside the ones MicroLock is defined to affect.
+  struct {
+    uint8_t a;
+    volatile uint8_t b;
+    MicroLock alock;
+    volatile uint8_t d;
+  } x;
+
+  uint8_t origB = 'b';
+  uint8_t origD = 'd';
+
+  x.a = 'a';
+  x.b = origB;
+  x.alock.init();
+  x.d = origD;
+
+  // This thread touches other parts of the host word to show that
+  // MicroLock does not interfere with memory outside of the byte
+  // it owns.
+  std::thread adjacentMemoryToucher = std::thread([&] {
+    startBarrier.wait();
+    for (unsigned iter = 0; iter < iterPerThread; ++iter) {
+      if (iter % 2) {
+        x.b++;
+      } else {
+        x.d++;
+      }
+    }
+  });
+
+  for (unsigned i = 0; i < nrThreads; ++i) {
+    threads.emplace_back([&] {
+      startBarrier.wait();
+      for (unsigned iter = 0; iter < iterPerThread; ++iter) {
+        unsigned slotNo = iter % 4;
+        x.alock.lock(slotNo);
+        counters[slotNo] += 1;
+        // The occasional sleep makes it more likely that we'll
+        // exercise the futex-wait path inside MicroLock.
+        if (iter % 1000 == 0) {
+          struct timespec ts = {0, 10000};
+          (void)nanosleep(&ts, nullptr);
+        }
+        x.alock.unlock(slotNo);
+      }
+    });
+  }
+
+  startBarrier.run();
+
+  for (auto it = threads.begin(); it != threads.end(); ++it) {
+    it->join();
+  }
+
+  adjacentMemoryToucher.join();
+
+  EXPECT_EQ(x.a, 'a');
+  EXPECT_EQ(x.b, (uint8_t)(origB + iterPerThread / 2));
+  EXPECT_EQ(x.d, (uint8_t)(origD + iterPerThread / 2));
+  for (unsigned i = 0; i < 4; ++i) {
+    EXPECT_EQ(counters[i], ((uint64_t)nrThreads * iterPerThread) / 4);
+  }
+}
+
+TEST(SmallLocks, MicroLockTryLock) {
+  MicroLock lock;
+  lock.init();
+  EXPECT_TRUE(lock.try_lock());
+  EXPECT_FALSE(lock.try_lock());
+  lock.unlock();
+}