SmallLocksTest.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. * Copyright 2011-present 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. #include <folly/synchronization/SmallLocks.h>
  17. #include <cassert>
  18. #include <condition_variable>
  19. #include <cstdio>
  20. #include <mutex>
  21. #include <string>
  22. #include <thread>
  23. #include <vector>
  24. #include <glog/logging.h>
  25. #include <folly/Random.h>
  26. #include <folly/portability/Asm.h>
  27. #include <folly/portability/GTest.h>
  28. #include <folly/portability/PThread.h>
  29. #include <folly/portability/Unistd.h>
  30. using folly::MicroLock;
  31. using folly::MicroSpinLock;
  32. using folly::MSLGuard;
  33. using std::string;
  34. #ifdef FOLLY_PICO_SPIN_LOCK_H_
  35. using folly::PicoSpinLock;
  36. #endif
  37. namespace {
  38. struct LockedVal {
  39. int ar[1024];
  40. MicroSpinLock lock;
  41. LockedVal() {
  42. lock.init();
  43. memset(ar, 0, sizeof ar);
  44. }
  45. };
  46. // Compile time test for packed struct support (requires that both of
  47. // these classes are POD).
  48. FOLLY_PACK_PUSH
  49. struct ignore1 {
  50. MicroSpinLock msl;
  51. int16_t foo;
  52. } FOLLY_PACK_ATTR;
  53. static_assert(sizeof(ignore1) == 3, "Size check failed");
  54. static_assert(sizeof(MicroSpinLock) == 1, "Size check failed");
  55. #ifdef FOLLY_PICO_SPIN_LOCK_H_
  56. struct ignore2 {
  57. PicoSpinLock<uint32_t> psl;
  58. int16_t foo;
  59. } FOLLY_PACK_ATTR;
  60. static_assert(sizeof(ignore2) == 6, "Size check failed");
  61. #endif
  62. FOLLY_PACK_POP
  63. LockedVal v;
  64. void splock_test() {
  65. const int max = 1000;
  66. auto rng = folly::ThreadLocalPRNG();
  67. for (int i = 0; i < max; i++) {
  68. folly::asm_volatile_pause();
  69. MSLGuard g(v.lock);
  70. int first = v.ar[0];
  71. for (size_t j = 1; j < sizeof v.ar / sizeof j; ++j) {
  72. EXPECT_EQ(first, v.ar[j]);
  73. }
  74. int byte = folly::Random::rand32(rng);
  75. memset(v.ar, char(byte), sizeof v.ar);
  76. }
  77. }
  78. #ifdef FOLLY_PICO_SPIN_LOCK_H_
  79. template <class T>
  80. struct PslTest {
  81. PicoSpinLock<T> lock;
  82. PslTest() {
  83. lock.init();
  84. }
  85. void doTest() {
  86. using UT = typename std::make_unsigned<T>::type;
  87. T ourVal = rand() % T(UT(1) << (sizeof(UT) * 8 - 1));
  88. for (int i = 0; i < 100; ++i) {
  89. std::lock_guard<PicoSpinLock<T>> guard(lock);
  90. lock.setData(ourVal);
  91. for (int n = 0; n < 10; ++n) {
  92. folly::asm_volatile_pause();
  93. EXPECT_EQ(lock.getData(), ourVal);
  94. }
  95. }
  96. }
  97. };
  98. template <class T>
  99. void doPslTest() {
  100. PslTest<T> testObj;
  101. const int nthrs = 17;
  102. std::vector<std::thread> threads;
  103. for (int i = 0; i < nthrs; ++i) {
  104. threads.push_back(std::thread(&PslTest<T>::doTest, &testObj));
  105. }
  106. for (auto& t : threads) {
  107. t.join();
  108. }
  109. }
  110. #endif
  111. struct TestClobber {
  112. TestClobber() {
  113. lock_.init();
  114. }
  115. void go() {
  116. std::lock_guard<MicroSpinLock> g(lock_);
  117. // This bug depends on gcc register allocation and is very sensitive. We
  118. // have to use DCHECK instead of EXPECT_*.
  119. DCHECK(!lock_.try_lock());
  120. }
  121. private:
  122. MicroSpinLock lock_;
  123. };
  124. } // namespace
  125. TEST(SmallLocks, SpinLockCorrectness) {
  126. EXPECT_EQ(sizeof(MicroSpinLock), 1);
  127. int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2;
  128. std::vector<std::thread> threads;
  129. for (int i = 0; i < nthrs; ++i) {
  130. threads.push_back(std::thread(splock_test));
  131. }
  132. for (auto& t : threads) {
  133. t.join();
  134. }
  135. }
  136. #ifdef FOLLY_PICO_SPIN_LOCK_H_
  137. TEST(SmallLocks, PicoSpinCorrectness) {
  138. doPslTest<int16_t>();
  139. doPslTest<uint16_t>();
  140. doPslTest<int32_t>();
  141. doPslTest<uint32_t>();
  142. doPslTest<int64_t>();
  143. doPslTest<uint64_t>();
  144. }
  145. TEST(SmallLocks, PicoSpinSigned) {
  146. typedef PicoSpinLock<int16_t, 0> Lock;
  147. Lock val;
  148. val.init(-4);
  149. EXPECT_EQ(val.getData(), -4);
  150. {
  151. std::lock_guard<Lock> guard(val);
  152. EXPECT_EQ(val.getData(), -4);
  153. val.setData(-8);
  154. EXPECT_EQ(val.getData(), -8);
  155. }
  156. EXPECT_EQ(val.getData(), -8);
  157. }
  158. #endif
  159. TEST(SmallLocks, RegClobber) {
  160. TestClobber().go();
  161. }
  162. FOLLY_PACK_PUSH
  163. #if defined(__SANITIZE_ADDRESS__) && !defined(__clang__) && \
  164. (defined(__GNUC__) || defined(__GNUG__))
  165. static_assert(sizeof(MicroLock) == 4, "Size check failed");
  166. #else
  167. static_assert(sizeof(MicroLock) == 1, "Size check failed");
  168. #endif
  169. FOLLY_PACK_POP
  170. namespace {
  171. struct SimpleBarrier {
  172. SimpleBarrier() : lock_(), cv_(), ready_(false) {}
  173. void wait() {
  174. std::unique_lock<std::mutex> lockHeld(lock_);
  175. while (!ready_) {
  176. cv_.wait(lockHeld);
  177. }
  178. }
  179. void run() {
  180. {
  181. std::unique_lock<std::mutex> lockHeld(lock_);
  182. ready_ = true;
  183. }
  184. cv_.notify_all();
  185. }
  186. private:
  187. std::mutex lock_;
  188. std::condition_variable cv_;
  189. bool ready_;
  190. };
  191. } // namespace
  192. TEST(SmallLocks, MicroLock) {
  193. volatile uint64_t counters[4] = {0, 0, 0, 0};
  194. std::vector<std::thread> threads;
  195. static const unsigned nrThreads = 20;
  196. static const unsigned iterPerThread = 10000;
  197. SimpleBarrier startBarrier;
  198. assert(iterPerThread % 4 == 0);
  199. // Embed the lock in a larger structure to ensure that we do not
  200. // affect bits outside the ones MicroLock is defined to affect.
  201. struct {
  202. uint8_t a;
  203. std::atomic<uint8_t> b;
  204. MicroLock alock;
  205. std::atomic<uint8_t> d;
  206. } x;
  207. uint8_t origB = 'b';
  208. uint8_t origD = 'd';
  209. x.a = 'a';
  210. x.b = origB;
  211. x.alock.init();
  212. x.d = origD;
  213. // This thread touches other parts of the host word to show that
  214. // MicroLock does not interfere with memory outside of the byte
  215. // it owns.
  216. std::thread adjacentMemoryToucher = std::thread([&] {
  217. startBarrier.wait();
  218. for (unsigned iter = 0; iter < iterPerThread; ++iter) {
  219. if (iter % 2) {
  220. x.b++;
  221. } else {
  222. x.d++;
  223. }
  224. }
  225. });
  226. for (unsigned i = 0; i < nrThreads; ++i) {
  227. threads.emplace_back([&] {
  228. startBarrier.wait();
  229. for (unsigned iter = 0; iter < iterPerThread; ++iter) {
  230. unsigned slotNo = iter % 4;
  231. x.alock.lock(slotNo);
  232. counters[slotNo] += 1;
  233. // The occasional sleep makes it more likely that we'll
  234. // exercise the futex-wait path inside MicroLock.
  235. if (iter % 1000 == 0) {
  236. struct timespec ts = {0, 10000};
  237. (void)nanosleep(&ts, nullptr);
  238. }
  239. x.alock.unlock(slotNo);
  240. }
  241. });
  242. }
  243. startBarrier.run();
  244. for (auto it = threads.begin(); it != threads.end(); ++it) {
  245. it->join();
  246. }
  247. adjacentMemoryToucher.join();
  248. EXPECT_EQ(x.a, 'a');
  249. EXPECT_EQ(x.b, (uint8_t)(origB + iterPerThread / 2));
  250. EXPECT_EQ(x.d, (uint8_t)(origD + iterPerThread / 2));
  251. for (unsigned i = 0; i < 4; ++i) {
  252. EXPECT_EQ(counters[i], ((uint64_t)nrThreads * iterPerThread) / 4);
  253. }
  254. }
  255. TEST(SmallLocks, MicroLockTryLock) {
  256. MicroLock lock;
  257. lock.init();
  258. EXPECT_TRUE(lock.try_lock());
  259. EXPECT_FALSE(lock.try_lock());
  260. lock.unlock();
  261. }