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