2 * Copyright 2011-present 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.
16 // @author: Andrei Alexandrescu (aalexandre)
18 // Test bed for folly/Synchronized.h
20 #include <folly/Synchronized.h>
21 #include <folly/LockTraitsBoost.h>
22 #include <folly/Portability.h>
23 #include <folly/SharedMutex.h>
24 #include <folly/SpinLock.h>
25 #include <folly/portability/GTest.h>
26 #include <folly/synchronization/RWSpinLock.h>
27 #include <folly/test/SynchronizedTestLib.h>
29 using namespace folly::sync_tests;
31 template <class Mutex>
32 class SynchronizedTest : public testing::Test {};
34 using SynchronizedTestTypes = testing::Types<
35 folly::SharedMutexReadPriority,
36 folly::SharedMutexWritePriority,
39 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
41 std::recursive_timed_mutex,
44 boost::recursive_mutex,
45 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
47 boost::recursive_timed_mutex,
49 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
50 folly::RWTicketSpinLock32,
51 folly::RWTicketSpinLock64,
55 TYPED_TEST_CASE(SynchronizedTest, SynchronizedTestTypes);
57 TYPED_TEST(SynchronizedTest, Basic) {
58 testBasic<TypeParam>();
61 TYPED_TEST(SynchronizedTest, WithLock) {
62 testWithLock<TypeParam>();
65 TYPED_TEST(SynchronizedTest, Unlock) {
66 testUnlock<TypeParam>();
69 TYPED_TEST(SynchronizedTest, Deprecated) {
70 testDeprecated<TypeParam>();
73 TYPED_TEST(SynchronizedTest, Concurrency) {
74 testConcurrency<TypeParam>();
77 TYPED_TEST(SynchronizedTest, AcquireLocked) {
78 testAcquireLocked<TypeParam>();
81 TYPED_TEST(SynchronizedTest, AcquireLockedWithConst) {
82 testAcquireLockedWithConst<TypeParam>();
85 TYPED_TEST(SynchronizedTest, DualLocking) {
86 testDualLocking<TypeParam>();
89 TYPED_TEST(SynchronizedTest, DualLockingWithConst) {
90 testDualLockingWithConst<TypeParam>();
93 TYPED_TEST(SynchronizedTest, ConstCopy) {
94 testConstCopy<TypeParam>();
97 TYPED_TEST(SynchronizedTest, InPlaceConstruction) {
98 testInPlaceConstruction<TypeParam>();
101 template <class Mutex>
102 class SynchronizedTimedTest : public testing::Test {};
104 using SynchronizedTimedTestTypes = testing::Types<
105 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
107 std::recursive_timed_mutex,
109 boost::recursive_timed_mutex,
112 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
113 folly::RWTicketSpinLock32,
114 folly::RWTicketSpinLock64,
116 folly::SharedMutexReadPriority,
117 folly::SharedMutexWritePriority>;
118 TYPED_TEST_CASE(SynchronizedTimedTest, SynchronizedTimedTestTypes);
120 TYPED_TEST(SynchronizedTimedTest, Timed) {
121 testTimed<TypeParam>();
124 TYPED_TEST(SynchronizedTimedTest, TimedSynchronized) {
125 testTimedSynchronized<TypeParam>();
128 template <class Mutex>
129 class SynchronizedTimedWithConstTest : public testing::Test {};
131 using SynchronizedTimedWithConstTestTypes = testing::Types<
132 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
135 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
136 folly::RWTicketSpinLock32,
137 folly::RWTicketSpinLock64,
139 folly::SharedMutexReadPriority,
140 folly::SharedMutexWritePriority>;
142 SynchronizedTimedWithConstTest, SynchronizedTimedWithConstTestTypes);
144 TYPED_TEST(SynchronizedTimedWithConstTest, TimedShared) {
145 testTimedShared<TypeParam>();
148 TYPED_TEST(SynchronizedTimedWithConstTest, TimedSynchronizeWithConst) {
149 testTimedSynchronizedWithConst<TypeParam>();
152 using CountPair = std::pair<int, int>;
153 // This class is specialized only to be uesed in SynchronizedLockTest
164 static CountPair getLockUnlockCount() {
165 return CountPair{lockCount_, unlockCount_};
168 static void resetLockUnlockCount() {
173 // Keep these two static for test access
174 // Keep them thread_local in case of tests are run in parallel within one
176 static FOLLY_TLS int lockCount_;
177 static FOLLY_TLS int unlockCount_;
179 FOLLY_TLS int FakeMutex::lockCount_{0};
180 FOLLY_TLS int FakeMutex::unlockCount_{0};
182 // SynchronizedLockTest is used to verify the correct lock unlock behavior
183 // happens per design
184 class SynchronizedLockTest : public testing::Test {
186 void SetUp() override {
187 FakeMutex::resetLockUnlockCount();
192 * Test mutex to help to automate assertions, taken from LockTraitsTest.cpp
194 class FakeAllPowerfulAssertingMutexInternal {
196 enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE };
199 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
200 this->lock_state = CurrentLockState::UNIQUE;
203 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
204 this->lock_state = CurrentLockState::UNLOCKED;
207 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
208 this->lock_state = CurrentLockState::SHARED;
210 void unlock_shared() {
211 EXPECT_EQ(this->lock_state, CurrentLockState::SHARED);
212 this->lock_state = CurrentLockState::UNLOCKED;
214 void lock_upgrade() {
215 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
216 this->lock_state = CurrentLockState::UPGRADE;
218 void unlock_upgrade() {
219 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
220 this->lock_state = CurrentLockState::UNLOCKED;
223 void unlock_upgrade_and_lock() {
224 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
225 this->lock_state = CurrentLockState::UNIQUE;
227 void unlock_and_lock_upgrade() {
228 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
229 this->lock_state = CurrentLockState::UPGRADE;
231 void unlock_and_lock_shared() {
232 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
233 this->lock_state = CurrentLockState::SHARED;
235 void unlock_upgrade_and_lock_shared() {
236 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
237 this->lock_state = CurrentLockState::SHARED;
240 template <class Rep, class Period>
241 bool try_lock_for(const std::chrono::duration<Rep, Period>&) {
242 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
243 this->lock_state = CurrentLockState::UNIQUE;
247 template <class Rep, class Period>
248 bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) {
249 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
250 this->lock_state = CurrentLockState::UPGRADE;
254 template <class Rep, class Period>
255 bool try_unlock_upgrade_and_lock_for(
256 const std::chrono::duration<Rep, Period>&) {
257 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
258 this->lock_state = CurrentLockState::UNIQUE;
263 * Initialize the FakeMutex with an unlocked state
265 CurrentLockState lock_state{CurrentLockState::UNLOCKED};
269 * The following works around the internal mutex for synchronized being
272 * This is horridly thread unsafe.
274 static FakeAllPowerfulAssertingMutexInternal globalAllPowerfulAssertingMutex;
276 class FakeAllPowerfulAssertingMutex {
279 globalAllPowerfulAssertingMutex.lock();
282 globalAllPowerfulAssertingMutex.unlock();
285 globalAllPowerfulAssertingMutex.lock_shared();
287 void unlock_shared() {
288 globalAllPowerfulAssertingMutex.unlock_shared();
290 void lock_upgrade() {
291 globalAllPowerfulAssertingMutex.lock_upgrade();
293 void unlock_upgrade() {
294 globalAllPowerfulAssertingMutex.unlock_upgrade();
297 void unlock_upgrade_and_lock() {
298 globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock();
300 void unlock_and_lock_upgrade() {
301 globalAllPowerfulAssertingMutex.unlock_and_lock_upgrade();
303 void unlock_and_lock_shared() {
304 globalAllPowerfulAssertingMutex.unlock_and_lock_shared();
306 void unlock_upgrade_and_lock_shared() {
307 globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock_shared();
310 template <class Rep, class Period>
311 bool try_lock_for(const std::chrono::duration<Rep, Period>& arg) {
312 return globalAllPowerfulAssertingMutex.try_lock_for(arg);
315 template <class Rep, class Period>
316 bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>& arg) {
317 return globalAllPowerfulAssertingMutex.try_lock_upgrade_for(arg);
320 template <class Rep, class Period>
321 bool try_unlock_upgrade_and_lock_for(
322 const std::chrono::duration<Rep, Period>& arg) {
323 return globalAllPowerfulAssertingMutex.try_unlock_upgrade_and_lock_for(arg);
326 // reset state on destruction
327 ~FakeAllPowerfulAssertingMutex() {
328 globalAllPowerfulAssertingMutex = FakeAllPowerfulAssertingMutexInternal{};
332 TEST_F(SynchronizedLockTest, TestCopyConstructibleValues) {
333 struct NonCopyConstructible {
334 NonCopyConstructible(const NonCopyConstructible&) = delete;
335 NonCopyConstructible& operator=(const NonCopyConstructible&) = delete;
337 struct CopyConstructible {};
338 EXPECT_FALSE(std::is_copy_constructible<
339 folly::Synchronized<NonCopyConstructible>>::value);
340 EXPECT_FALSE(std::is_copy_assignable<
341 folly::Synchronized<NonCopyConstructible>>::value);
342 EXPECT_TRUE(std::is_copy_constructible<
343 folly::Synchronized<CopyConstructible>>::value);
345 std::is_copy_assignable<folly::Synchronized<CopyConstructible>>::value);
348 TEST_F(SynchronizedLockTest, UpgradableLocking) {
349 folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
353 std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
354 "The ulock function was not well configured, blame aary@instagram.com");
357 auto ulock = sync.ulock();
359 globalAllPowerfulAssertingMutex.lock_state,
360 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
363 // should be unlocked here
365 globalAllPowerfulAssertingMutex.lock_state,
366 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
368 // test going from upgrade to exclusive
370 auto ulock = sync.ulock();
371 auto wlock = ulock.moveFromUpgradeToWrite();
372 EXPECT_EQ(static_cast<bool>(ulock), false);
374 globalAllPowerfulAssertingMutex.lock_state,
375 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
378 // should be unlocked here
380 globalAllPowerfulAssertingMutex.lock_state,
381 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
383 // test going from upgrade to shared
385 auto ulock = sync.ulock();
386 auto slock = ulock.moveFromUpgradeToRead();
387 EXPECT_EQ(static_cast<bool>(ulock), false);
389 globalAllPowerfulAssertingMutex.lock_state,
390 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
393 // should be unlocked here
395 globalAllPowerfulAssertingMutex.lock_state,
396 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
398 // test going from exclusive to upgrade
400 auto wlock = sync.wlock();
401 auto ulock = wlock.moveFromWriteToUpgrade();
402 EXPECT_EQ(static_cast<bool>(wlock), false);
404 globalAllPowerfulAssertingMutex.lock_state,
405 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
408 // should be unlocked here
410 globalAllPowerfulAssertingMutex.lock_state,
411 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
413 // test going from exclusive to shared
415 auto wlock = sync.wlock();
416 auto slock = wlock.moveFromWriteToRead();
417 EXPECT_EQ(static_cast<bool>(wlock), false);
419 globalAllPowerfulAssertingMutex.lock_state,
420 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
423 // should be unlocked here
425 globalAllPowerfulAssertingMutex.lock_state,
426 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
429 TEST_F(SynchronizedLockTest, UpgradableLockingWithULock) {
430 folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
434 std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
435 "The ulock function was not well configured, blame aary@instagram.com");
437 // test from upgrade to write
438 sync.withULockPtr([](auto ulock) {
439 EXPECT_EQ(static_cast<bool>(ulock), true);
441 globalAllPowerfulAssertingMutex.lock_state,
442 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
444 auto wlock = ulock.moveFromUpgradeToWrite();
445 EXPECT_EQ(static_cast<bool>(ulock), false);
447 globalAllPowerfulAssertingMutex.lock_state,
448 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
451 // should be unlocked here
453 globalAllPowerfulAssertingMutex.lock_state,
454 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
456 // test from write to upgrade
457 sync.withWLockPtr([](auto wlock) {
458 EXPECT_EQ(static_cast<bool>(wlock), true);
460 globalAllPowerfulAssertingMutex.lock_state,
461 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
463 auto ulock = wlock.moveFromWriteToUpgrade();
464 EXPECT_EQ(static_cast<bool>(wlock), false);
466 globalAllPowerfulAssertingMutex.lock_state,
467 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
470 // should be unlocked here
472 globalAllPowerfulAssertingMutex.lock_state,
473 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
475 // test from upgrade to shared
476 sync.withULockPtr([](auto ulock) {
477 EXPECT_EQ(static_cast<bool>(ulock), true);
479 globalAllPowerfulAssertingMutex.lock_state,
480 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
482 auto slock = ulock.moveFromUpgradeToRead();
483 EXPECT_EQ(static_cast<bool>(ulock), false);
485 globalAllPowerfulAssertingMutex.lock_state,
486 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
489 // should be unlocked here
491 globalAllPowerfulAssertingMutex.lock_state,
492 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
494 // test from write to shared
495 sync.withWLockPtr([](auto wlock) {
496 EXPECT_EQ(static_cast<bool>(wlock), true);
498 globalAllPowerfulAssertingMutex.lock_state,
499 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
501 auto slock = wlock.moveFromWriteToRead();
502 EXPECT_EQ(static_cast<bool>(wlock), false);
504 globalAllPowerfulAssertingMutex.lock_state,
505 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
508 // should be unlocked here
510 globalAllPowerfulAssertingMutex.lock_state,
511 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);