2 * Copyright 2016 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 // @author: Andrei Alexandrescu (aalexandre)
19 // Test bed for folly/Synchronized.h
21 #include <folly/LockTraitsBoost.h>
22 #include <folly/Portability.h>
23 #include <folly/RWSpinLock.h>
24 #include <folly/SharedMutex.h>
25 #include <folly/SpinLock.h>
26 #include <folly/Synchronized.h>
27 #include <folly/test/SynchronizedTestLib.h>
28 #include <gtest/gtest.h>
30 using namespace folly::sync_tests;
32 template <class Mutex>
33 class SynchronizedTest : public testing::Test {};
35 using SynchronizedTestTypes = testing::Types<
36 folly::SharedMutexReadPriority,
37 folly::SharedMutexWritePriority,
40 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
42 std::recursive_timed_mutex,
45 boost::recursive_mutex,
46 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
48 boost::recursive_timed_mutex,
50 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
51 folly::RWTicketSpinLock32,
52 folly::RWTicketSpinLock64,
56 TYPED_TEST_CASE(SynchronizedTest, SynchronizedTestTypes);
58 TYPED_TEST(SynchronizedTest, Basic) {
59 testBasic<TypeParam>();
62 TYPED_TEST(SynchronizedTest, WithLock) {
63 testWithLock<TypeParam>();
66 TYPED_TEST(SynchronizedTest, Unlock) {
67 testUnlock<TypeParam>();
70 TYPED_TEST(SynchronizedTest, Deprecated) {
71 testDeprecated<TypeParam>();
74 TYPED_TEST(SynchronizedTest, Concurrency) {
75 testConcurrency<TypeParam>();
78 TYPED_TEST(SynchronizedTest, AcquireLocked) {
79 testAcquireLocked<TypeParam>();
82 TYPED_TEST(SynchronizedTest, AcquireLockedWithConst) {
83 testAcquireLockedWithConst<TypeParam>();
86 TYPED_TEST(SynchronizedTest, DualLocking) {
87 testDualLocking<TypeParam>();
90 TYPED_TEST(SynchronizedTest, DualLockingWithConst) {
91 testDualLockingWithConst<TypeParam>();
94 TYPED_TEST(SynchronizedTest, ConstCopy) {
95 testConstCopy<TypeParam>();
98 template <class Mutex>
99 class SynchronizedTimedTest : public testing::Test {};
101 using SynchronizedTimedTestTypes = testing::Types<
102 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
104 std::recursive_timed_mutex,
106 boost::recursive_timed_mutex,
109 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
110 folly::RWTicketSpinLock32,
111 folly::RWTicketSpinLock64,
113 folly::SharedMutexReadPriority,
114 folly::SharedMutexWritePriority>;
115 TYPED_TEST_CASE(SynchronizedTimedTest, SynchronizedTimedTestTypes);
117 TYPED_TEST(SynchronizedTimedTest, Timed) {
118 testTimed<TypeParam>();
121 TYPED_TEST(SynchronizedTimedTest, TimedSynchronized) {
122 testTimedSynchronized<TypeParam>();
125 template <class Mutex>
126 class SynchronizedTimedWithConstTest : public testing::Test {};
128 using SynchronizedTimedWithConstTestTypes = testing::Types<
129 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
132 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
133 folly::RWTicketSpinLock32,
134 folly::RWTicketSpinLock64,
136 folly::SharedMutexReadPriority,
137 folly::SharedMutexWritePriority>;
139 SynchronizedTimedWithConstTest, SynchronizedTimedWithConstTestTypes);
141 TYPED_TEST(SynchronizedTimedWithConstTest, TimedShared) {
142 testTimedShared<TypeParam>();
145 TYPED_TEST(SynchronizedTimedWithConstTest, TimedSynchronizeWithConst) {
146 testTimedSynchronizedWithConst<TypeParam>();
149 TYPED_TEST(SynchronizedTest, InPlaceConstruction) {
150 testInPlaceConstruction<TypeParam>();
153 using CountPair = std::pair<int, int>;
154 // This class is specialized only to be uesed in SynchronizedLockTest
165 static CountPair getLockUnlockCount() {
166 return CountPair{lockCount_, unlockCount_};
169 static void resetLockUnlockCount() {
174 // Keep these two static for test access
175 // Keep them thread_local in case of tests are run in parallel within one
177 static FOLLY_TLS int lockCount_;
178 static FOLLY_TLS int unlockCount_;
180 FOLLY_TLS int FakeMutex::lockCount_{0};
181 FOLLY_TLS int FakeMutex::unlockCount_{0};
183 // SynchronizedLockTest is used to verify the correct lock unlock behavior
184 // happens per design
185 class SynchronizedLockTest : public testing::Test {
187 void SetUp() override {
188 FakeMutex::resetLockUnlockCount();
193 * Test mutex to help to automate assertions, taken from LockTraitsTest.cpp
195 class FakeAllPowerfulAssertingMutexInternal {
197 enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE };
200 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
201 this->lock_state = CurrentLockState::UNIQUE;
204 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
205 this->lock_state = CurrentLockState::UNLOCKED;
208 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
209 this->lock_state = CurrentLockState::SHARED;
211 void unlock_shared() {
212 EXPECT_EQ(this->lock_state, CurrentLockState::SHARED);
213 this->lock_state = CurrentLockState::UNLOCKED;
215 void lock_upgrade() {
216 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
217 this->lock_state = CurrentLockState::UPGRADE;
219 void unlock_upgrade() {
220 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
221 this->lock_state = CurrentLockState::UNLOCKED;
224 void unlock_upgrade_and_lock() {
225 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
226 this->lock_state = CurrentLockState::UNIQUE;
228 void unlock_and_lock_upgrade() {
229 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
230 this->lock_state = CurrentLockState::UPGRADE;
232 void unlock_and_lock_shared() {
233 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
234 this->lock_state = CurrentLockState::SHARED;
236 void unlock_upgrade_and_lock_shared() {
237 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
238 this->lock_state = CurrentLockState::SHARED;
241 template <class Rep, class Period>
242 bool try_lock_for(const std::chrono::duration<Rep, Period>&) {
243 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
244 this->lock_state = CurrentLockState::UNIQUE;
248 template <class Rep, class Period>
249 bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) {
250 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
251 this->lock_state = CurrentLockState::UPGRADE;
255 template <class Rep, class Period>
256 bool try_unlock_upgrade_and_lock_for(
257 const std::chrono::duration<Rep, Period>&) {
258 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
259 this->lock_state = CurrentLockState::UNIQUE;
264 * Initialize the FakeMutex with an unlocked state
266 CurrentLockState lock_state{CurrentLockState::UNLOCKED};
270 * The following works around the internal mutex for synchronized being
273 * This is horridly thread unsafe.
275 static FakeAllPowerfulAssertingMutexInternal globalAllPowerfulAssertingMutex;
277 class FakeAllPowerfulAssertingMutex {
280 globalAllPowerfulAssertingMutex.lock();
283 globalAllPowerfulAssertingMutex.unlock();
286 globalAllPowerfulAssertingMutex.lock_shared();
288 void unlock_shared() {
289 globalAllPowerfulAssertingMutex.unlock_shared();
291 void lock_upgrade() {
292 globalAllPowerfulAssertingMutex.lock_upgrade();
294 void unlock_upgrade() {
295 globalAllPowerfulAssertingMutex.unlock_upgrade();
298 void unlock_upgrade_and_lock() {
299 globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock();
301 void unlock_and_lock_upgrade() {
302 globalAllPowerfulAssertingMutex.unlock_and_lock_upgrade();
304 void unlock_and_lock_shared() {
305 globalAllPowerfulAssertingMutex.unlock_and_lock_shared();
307 void unlock_upgrade_and_lock_shared() {
308 globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock_shared();
311 template <class Rep, class Period>
312 bool try_lock_for(const std::chrono::duration<Rep, Period>& arg) {
313 return globalAllPowerfulAssertingMutex.try_lock_for(arg);
316 template <class Rep, class Period>
317 bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>& arg) {
318 return globalAllPowerfulAssertingMutex.try_lock_upgrade_for(arg);
321 template <class Rep, class Period>
322 bool try_unlock_upgrade_and_lock_for(
323 const std::chrono::duration<Rep, Period>& arg) {
324 return globalAllPowerfulAssertingMutex.try_unlock_upgrade_and_lock_for(arg);
327 // reset state on destruction
328 ~FakeAllPowerfulAssertingMutex() {
329 globalAllPowerfulAssertingMutex = FakeAllPowerfulAssertingMutexInternal{};
333 // Single level of SYNCHRONIZED and UNSYNCHRONIZED, although nested test are
334 // super set of it, it is possible single level test passes while nested tests
336 TEST_F(SynchronizedLockTest, SyncUnSync) {
337 folly::Synchronized<std::vector<int>, FakeMutex> obj;
338 EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount());
340 EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount());
341 UNSYNCHRONIZED(obj) {
342 EXPECT_EQ((CountPair{1, 1}), FakeMutex::getLockUnlockCount());
344 EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount());
346 EXPECT_EQ((CountPair{2, 2}), FakeMutex::getLockUnlockCount());
349 // Nested SYNCHRONIZED UNSYNCHRONIZED test, 2 levels of synchronization
350 TEST_F(SynchronizedLockTest, NestedSyncUnSync) {
351 folly::Synchronized<std::vector<int>, FakeMutex> obj;
352 EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount());
353 SYNCHRONIZED(objCopy, obj) {
354 EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount());
356 EXPECT_EQ((CountPair{2, 0}), FakeMutex::getLockUnlockCount());
357 // Note: UNSYNCHRONIZED has always been kind of broken here.
358 // The input parameter is ignored (other than to overwrite what the input
359 // variable name refers to), and it unlocks the most object acquired in
360 // the most recent SYNCHRONIZED scope.
361 UNSYNCHRONIZED(obj) {
362 EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount());
364 EXPECT_EQ((CountPair{3, 1}), FakeMutex::getLockUnlockCount());
365 UNSYNCHRONIZED(obj) {
366 EXPECT_EQ((CountPair{3, 2}), FakeMutex::getLockUnlockCount());
368 EXPECT_EQ((CountPair{4, 2}), FakeMutex::getLockUnlockCount());
370 EXPECT_EQ((CountPair{4, 3}), FakeMutex::getLockUnlockCount());
372 EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount());
375 // Different nesting behavior, UNSYNCHRONIZED called on different depth of
377 TEST_F(SynchronizedLockTest, NestedSyncUnSync2) {
378 folly::Synchronized<std::vector<int>, FakeMutex> obj;
379 EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount());
380 SYNCHRONIZED(objCopy, obj) {
381 EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount());
383 EXPECT_EQ((CountPair{2, 0}), FakeMutex::getLockUnlockCount());
384 UNSYNCHRONIZED(obj) {
385 EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount());
387 EXPECT_EQ((CountPair{3, 1}), FakeMutex::getLockUnlockCount());
389 EXPECT_EQ((CountPair{3, 2}), FakeMutex::getLockUnlockCount());
390 UNSYNCHRONIZED(obj) {
391 EXPECT_EQ((CountPair{3, 3}), FakeMutex::getLockUnlockCount());
393 EXPECT_EQ((CountPair{4, 3}), FakeMutex::getLockUnlockCount());
395 EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount());
398 TEST_F(SynchronizedLockTest, UpgradableLocking) {
399 folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
403 std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
404 "The ulock function was not well configured, blame aary@instagram.com");
407 auto ulock = sync.ulock();
409 globalAllPowerfulAssertingMutex.lock_state,
410 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
413 // should be unlocked here
415 globalAllPowerfulAssertingMutex.lock_state,
416 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
418 // test going from upgrade to exclusive
420 auto ulock = sync.ulock();
421 auto wlock = ulock.moveFromUpgradeToWrite();
422 EXPECT_EQ(static_cast<bool>(ulock), false);
424 globalAllPowerfulAssertingMutex.lock_state,
425 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
428 // should be unlocked here
430 globalAllPowerfulAssertingMutex.lock_state,
431 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
433 // test going from upgrade to shared
435 auto ulock = sync.ulock();
436 auto slock = ulock.moveFromUpgradeToShared();
437 EXPECT_EQ(static_cast<bool>(ulock), false);
439 globalAllPowerfulAssertingMutex.lock_state,
440 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
443 // should be unlocked here
445 globalAllPowerfulAssertingMutex.lock_state,
446 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
448 // test going from exclusive to upgrade
450 auto wlock = sync.wlock();
451 auto ulock = wlock.moveFromWriteToUpgrade();
452 EXPECT_EQ(static_cast<bool>(wlock), false);
454 globalAllPowerfulAssertingMutex.lock_state,
455 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
458 // should be unlocked here
460 globalAllPowerfulAssertingMutex.lock_state,
461 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
463 // test going from exclusive to shared
465 auto wlock = sync.wlock();
466 auto slock = wlock.moveFromWriteToShared();
467 EXPECT_EQ(static_cast<bool>(wlock), false);
469 globalAllPowerfulAssertingMutex.lock_state,
470 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
473 // should be unlocked here
475 globalAllPowerfulAssertingMutex.lock_state,
476 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
479 TEST_F(SynchronizedLockTest, UpgradableLockingWithULock) {
480 folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
484 std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
485 "The ulock function was not well configured, blame aary@instagram.com");
487 // test from upgrade to write
488 sync.withULockPtr([](auto ulock) {
489 EXPECT_EQ(static_cast<bool>(ulock), true);
491 globalAllPowerfulAssertingMutex.lock_state,
492 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
494 auto wlock = ulock.moveFromUpgradeToWrite();
495 EXPECT_EQ(static_cast<bool>(ulock), false);
497 globalAllPowerfulAssertingMutex.lock_state,
498 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
501 // should be unlocked here
503 globalAllPowerfulAssertingMutex.lock_state,
504 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
506 // test from write to upgrade
507 sync.withWLockPtr([](auto wlock) {
508 EXPECT_EQ(static_cast<bool>(wlock), true);
510 globalAllPowerfulAssertingMutex.lock_state,
511 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
513 auto ulock = wlock.moveFromWriteToUpgrade();
514 EXPECT_EQ(static_cast<bool>(wlock), false);
516 globalAllPowerfulAssertingMutex.lock_state,
517 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
520 // should be unlocked here
522 globalAllPowerfulAssertingMutex.lock_state,
523 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
525 // test from upgrade to shared
526 sync.withULockPtr([](auto ulock) {
527 EXPECT_EQ(static_cast<bool>(ulock), true);
529 globalAllPowerfulAssertingMutex.lock_state,
530 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
532 auto slock = ulock.moveFromUpgradeToShared();
533 EXPECT_EQ(static_cast<bool>(ulock), false);
535 globalAllPowerfulAssertingMutex.lock_state,
536 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
539 // should be unlocked here
541 globalAllPowerfulAssertingMutex.lock_state,
542 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
544 // test from write to shared
545 sync.withWLockPtr([](auto wlock) {
546 EXPECT_EQ(static_cast<bool>(wlock), true);
548 globalAllPowerfulAssertingMutex.lock_state,
549 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
551 auto slock = wlock.moveFromWriteToShared();
552 EXPECT_EQ(static_cast<bool>(wlock), false);
554 globalAllPowerfulAssertingMutex.lock_state,
555 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
558 // should be unlocked here
560 globalAllPowerfulAssertingMutex.lock_state,
561 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);