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 TYPED_TEST(SynchronizedTest, Exchange) {
102 testExchange<TypeParam>();
105 template <class Mutex>
106 class SynchronizedTimedTest : public testing::Test {};
108 using SynchronizedTimedTestTypes = testing::Types<
109 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
111 std::recursive_timed_mutex,
113 boost::recursive_timed_mutex,
116 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
117 folly::RWTicketSpinLock32,
118 folly::RWTicketSpinLock64,
120 folly::SharedMutexReadPriority,
121 folly::SharedMutexWritePriority>;
122 TYPED_TEST_CASE(SynchronizedTimedTest, SynchronizedTimedTestTypes);
124 TYPED_TEST(SynchronizedTimedTest, Timed) {
125 testTimed<TypeParam>();
128 TYPED_TEST(SynchronizedTimedTest, TimedSynchronized) {
129 testTimedSynchronized<TypeParam>();
132 template <class Mutex>
133 class SynchronizedTimedWithConstTest : public testing::Test {};
135 using SynchronizedTimedWithConstTestTypes = testing::Types<
136 #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES
139 #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
140 folly::RWTicketSpinLock32,
141 folly::RWTicketSpinLock64,
143 folly::SharedMutexReadPriority,
144 folly::SharedMutexWritePriority>;
146 SynchronizedTimedWithConstTest, SynchronizedTimedWithConstTestTypes);
148 TYPED_TEST(SynchronizedTimedWithConstTest, TimedShared) {
149 testTimedShared<TypeParam>();
152 TYPED_TEST(SynchronizedTimedWithConstTest, TimedSynchronizeWithConst) {
153 testTimedSynchronizedWithConst<TypeParam>();
156 using CountPair = std::pair<int, int>;
157 // This class is specialized only to be uesed in SynchronizedLockTest
168 static CountPair getLockUnlockCount() {
169 return CountPair{lockCount_, unlockCount_};
172 static void resetLockUnlockCount() {
177 // Keep these two static for test access
178 // Keep them thread_local in case of tests are run in parallel within one
180 static FOLLY_TLS int lockCount_;
181 static FOLLY_TLS int unlockCount_;
183 FOLLY_TLS int FakeMutex::lockCount_{0};
184 FOLLY_TLS int FakeMutex::unlockCount_{0};
186 // SynchronizedLockTest is used to verify the correct lock unlock behavior
187 // happens per design
188 class SynchronizedLockTest : public testing::Test {
190 void SetUp() override {
191 FakeMutex::resetLockUnlockCount();
196 * Test mutex to help to automate assertions, taken from LockTraitsTest.cpp
198 class FakeAllPowerfulAssertingMutexInternal {
200 enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE };
203 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
204 this->lock_state = CurrentLockState::UNIQUE;
207 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
208 this->lock_state = CurrentLockState::UNLOCKED;
211 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
212 this->lock_state = CurrentLockState::SHARED;
214 void unlock_shared() {
215 EXPECT_EQ(this->lock_state, CurrentLockState::SHARED);
216 this->lock_state = CurrentLockState::UNLOCKED;
218 void lock_upgrade() {
219 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
220 this->lock_state = CurrentLockState::UPGRADE;
222 void unlock_upgrade() {
223 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
224 this->lock_state = CurrentLockState::UNLOCKED;
227 void unlock_upgrade_and_lock() {
228 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
229 this->lock_state = CurrentLockState::UNIQUE;
231 void unlock_and_lock_upgrade() {
232 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
233 this->lock_state = CurrentLockState::UPGRADE;
235 void unlock_and_lock_shared() {
236 EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE);
237 this->lock_state = CurrentLockState::SHARED;
239 void unlock_upgrade_and_lock_shared() {
240 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
241 this->lock_state = CurrentLockState::SHARED;
244 template <class Rep, class Period>
245 bool try_lock_for(const std::chrono::duration<Rep, Period>&) {
246 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
247 this->lock_state = CurrentLockState::UNIQUE;
251 template <class Rep, class Period>
252 bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) {
253 EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED);
254 this->lock_state = CurrentLockState::UPGRADE;
258 template <class Rep, class Period>
259 bool try_unlock_upgrade_and_lock_for(
260 const std::chrono::duration<Rep, Period>&) {
261 EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE);
262 this->lock_state = CurrentLockState::UNIQUE;
267 * Initialize the FakeMutex with an unlocked state
269 CurrentLockState lock_state{CurrentLockState::UNLOCKED};
273 * The following works around the internal mutex for synchronized being
276 * This is horridly thread unsafe.
278 static FakeAllPowerfulAssertingMutexInternal globalAllPowerfulAssertingMutex;
280 class FakeAllPowerfulAssertingMutex {
283 globalAllPowerfulAssertingMutex.lock();
286 globalAllPowerfulAssertingMutex.unlock();
289 globalAllPowerfulAssertingMutex.lock_shared();
291 void unlock_shared() {
292 globalAllPowerfulAssertingMutex.unlock_shared();
294 void lock_upgrade() {
295 globalAllPowerfulAssertingMutex.lock_upgrade();
297 void unlock_upgrade() {
298 globalAllPowerfulAssertingMutex.unlock_upgrade();
301 void unlock_upgrade_and_lock() {
302 globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock();
304 void unlock_and_lock_upgrade() {
305 globalAllPowerfulAssertingMutex.unlock_and_lock_upgrade();
307 void unlock_and_lock_shared() {
308 globalAllPowerfulAssertingMutex.unlock_and_lock_shared();
310 void unlock_upgrade_and_lock_shared() {
311 globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock_shared();
314 template <class Rep, class Period>
315 bool try_lock_for(const std::chrono::duration<Rep, Period>& arg) {
316 return globalAllPowerfulAssertingMutex.try_lock_for(arg);
319 template <class Rep, class Period>
320 bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>& arg) {
321 return globalAllPowerfulAssertingMutex.try_lock_upgrade_for(arg);
324 template <class Rep, class Period>
325 bool try_unlock_upgrade_and_lock_for(
326 const std::chrono::duration<Rep, Period>& arg) {
327 return globalAllPowerfulAssertingMutex.try_unlock_upgrade_and_lock_for(arg);
330 // reset state on destruction
331 ~FakeAllPowerfulAssertingMutex() {
332 globalAllPowerfulAssertingMutex = FakeAllPowerfulAssertingMutexInternal{};
336 TEST_F(SynchronizedLockTest, TestCopyConstructibleValues) {
337 struct NonCopyConstructible {
338 NonCopyConstructible(const NonCopyConstructible&) = delete;
339 NonCopyConstructible& operator=(const NonCopyConstructible&) = delete;
341 struct CopyConstructible {};
342 EXPECT_FALSE(std::is_copy_constructible<
343 folly::Synchronized<NonCopyConstructible>>::value);
344 EXPECT_FALSE(std::is_copy_assignable<
345 folly::Synchronized<NonCopyConstructible>>::value);
346 EXPECT_TRUE(std::is_copy_constructible<
347 folly::Synchronized<CopyConstructible>>::value);
349 std::is_copy_assignable<folly::Synchronized<CopyConstructible>>::value);
352 TEST_F(SynchronizedLockTest, UpgradableLocking) {
353 folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
357 std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
358 "The ulock function was not well configured, blame aary@instagram.com");
361 auto ulock = sync.ulock();
363 globalAllPowerfulAssertingMutex.lock_state,
364 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
367 // should be unlocked here
369 globalAllPowerfulAssertingMutex.lock_state,
370 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
372 // test going from upgrade to exclusive
374 auto ulock = sync.ulock();
375 auto wlock = ulock.moveFromUpgradeToWrite();
376 EXPECT_EQ(static_cast<bool>(ulock), false);
378 globalAllPowerfulAssertingMutex.lock_state,
379 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
382 // should be unlocked here
384 globalAllPowerfulAssertingMutex.lock_state,
385 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
387 // test going from upgrade to shared
389 auto ulock = sync.ulock();
390 auto slock = ulock.moveFromUpgradeToRead();
391 EXPECT_EQ(static_cast<bool>(ulock), false);
393 globalAllPowerfulAssertingMutex.lock_state,
394 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
397 // should be unlocked here
399 globalAllPowerfulAssertingMutex.lock_state,
400 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
402 // test going from exclusive to upgrade
404 auto wlock = sync.wlock();
405 auto ulock = wlock.moveFromWriteToUpgrade();
406 EXPECT_EQ(static_cast<bool>(wlock), false);
408 globalAllPowerfulAssertingMutex.lock_state,
409 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
412 // should be unlocked here
414 globalAllPowerfulAssertingMutex.lock_state,
415 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
417 // test going from exclusive to shared
419 auto wlock = sync.wlock();
420 auto slock = wlock.moveFromWriteToRead();
421 EXPECT_EQ(static_cast<bool>(wlock), false);
423 globalAllPowerfulAssertingMutex.lock_state,
424 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
427 // should be unlocked here
429 globalAllPowerfulAssertingMutex.lock_state,
430 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
433 TEST_F(SynchronizedLockTest, UpgradableLockingWithULock) {
434 folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync;
438 std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value,
439 "The ulock function was not well configured, blame aary@instagram.com");
441 // test from upgrade to write
442 sync.withULockPtr([](auto ulock) {
443 EXPECT_EQ(static_cast<bool>(ulock), true);
445 globalAllPowerfulAssertingMutex.lock_state,
446 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
448 auto wlock = ulock.moveFromUpgradeToWrite();
449 EXPECT_EQ(static_cast<bool>(ulock), false);
451 globalAllPowerfulAssertingMutex.lock_state,
452 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
455 // should be unlocked here
457 globalAllPowerfulAssertingMutex.lock_state,
458 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
460 // test from write to upgrade
461 sync.withWLockPtr([](auto wlock) {
462 EXPECT_EQ(static_cast<bool>(wlock), true);
464 globalAllPowerfulAssertingMutex.lock_state,
465 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
467 auto ulock = wlock.moveFromWriteToUpgrade();
468 EXPECT_EQ(static_cast<bool>(wlock), false);
470 globalAllPowerfulAssertingMutex.lock_state,
471 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
474 // should be unlocked here
476 globalAllPowerfulAssertingMutex.lock_state,
477 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
479 // test from upgrade to shared
480 sync.withULockPtr([](auto ulock) {
481 EXPECT_EQ(static_cast<bool>(ulock), true);
483 globalAllPowerfulAssertingMutex.lock_state,
484 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE);
486 auto slock = ulock.moveFromUpgradeToRead();
487 EXPECT_EQ(static_cast<bool>(ulock), false);
489 globalAllPowerfulAssertingMutex.lock_state,
490 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
493 // should be unlocked here
495 globalAllPowerfulAssertingMutex.lock_state,
496 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);
498 // test from write to shared
499 sync.withWLockPtr([](auto wlock) {
500 EXPECT_EQ(static_cast<bool>(wlock), true);
502 globalAllPowerfulAssertingMutex.lock_state,
503 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE);
505 auto slock = wlock.moveFromWriteToRead();
506 EXPECT_EQ(static_cast<bool>(wlock), false);
508 globalAllPowerfulAssertingMutex.lock_state,
509 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED);
512 // should be unlocked here
514 globalAllPowerfulAssertingMutex.lock_state,
515 FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED);