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 | // @author: Andrei Alexandrescu (aalexandre) |
17 | |
18 | // Test bed for folly/Synchronized.h |
19 | |
20 | #include <folly/Synchronized.h> |
21 | #include <folly/Function.h> |
22 | #include <folly/LockTraitsBoost.h> |
23 | #include <folly/Portability.h> |
24 | #include <folly/ScopeGuard.h> |
25 | #include <folly/SharedMutex.h> |
26 | #include <folly/SpinLock.h> |
27 | #include <folly/portability/GTest.h> |
28 | #include <folly/synchronization/RWSpinLock.h> |
29 | #include <folly/test/SynchronizedTestLib.h> |
30 | |
31 | using namespace folly::sync_tests; |
32 | |
33 | namespace folly { |
34 | |
35 | template <class Mutex> |
36 | class SynchronizedTest : public testing::Test {}; |
37 | |
38 | using SynchronizedTestTypes = testing::Types< |
39 | folly::SharedMutexReadPriority, |
40 | folly::SharedMutexWritePriority, |
41 | std::mutex, |
42 | std::recursive_mutex, |
43 | #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES |
44 | std::timed_mutex, |
45 | std::recursive_timed_mutex, |
46 | #endif |
47 | boost::mutex, |
48 | boost::recursive_mutex, |
49 | #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES |
50 | boost::timed_mutex, |
51 | boost::recursive_timed_mutex, |
52 | #endif |
53 | #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_ |
54 | folly::RWTicketSpinLock32, |
55 | folly::RWTicketSpinLock64, |
56 | #endif |
57 | boost::shared_mutex, |
58 | folly::SpinLock>; |
59 | TYPED_TEST_CASE(SynchronizedTest, SynchronizedTestTypes); |
60 | |
61 | TYPED_TEST(SynchronizedTest, Basic) { |
62 | testBasic<TypeParam>(); |
63 | } |
64 | |
65 | TYPED_TEST(SynchronizedTest, WithLock) { |
66 | testWithLock<TypeParam>(); |
67 | } |
68 | |
69 | TYPED_TEST(SynchronizedTest, Unlock) { |
70 | testUnlock<TypeParam>(); |
71 | } |
72 | |
73 | TYPED_TEST(SynchronizedTest, Deprecated) { |
74 | testDeprecated<TypeParam>(); |
75 | } |
76 | |
77 | TYPED_TEST(SynchronizedTest, Concurrency) { |
78 | testConcurrency<TypeParam>(); |
79 | } |
80 | |
81 | TYPED_TEST(SynchronizedTest, AcquireLocked) { |
82 | testAcquireLocked<TypeParam>(); |
83 | } |
84 | |
85 | TYPED_TEST(SynchronizedTest, AcquireLockedWithConst) { |
86 | testAcquireLockedWithConst<TypeParam>(); |
87 | } |
88 | |
89 | TYPED_TEST(SynchronizedTest, DualLocking) { |
90 | testDualLocking<TypeParam>(); |
91 | } |
92 | |
93 | TYPED_TEST(SynchronizedTest, DualLockingWithConst) { |
94 | testDualLockingWithConst<TypeParam>(); |
95 | } |
96 | |
97 | TYPED_TEST(SynchronizedTest, ConstCopy) { |
98 | testConstCopy<TypeParam>(); |
99 | } |
100 | |
101 | TYPED_TEST(SynchronizedTest, InPlaceConstruction) { |
102 | testInPlaceConstruction<TypeParam>(); |
103 | } |
104 | |
105 | TYPED_TEST(SynchronizedTest, Exchange) { |
106 | testExchange<TypeParam>(); |
107 | } |
108 | |
109 | template <class Mutex> |
110 | class SynchronizedTimedTest : public testing::Test {}; |
111 | |
112 | using SynchronizedTimedTestTypes = testing::Types< |
113 | #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES |
114 | std::timed_mutex, |
115 | std::recursive_timed_mutex, |
116 | boost::timed_mutex, |
117 | boost::recursive_timed_mutex, |
118 | boost::shared_mutex, |
119 | #endif |
120 | #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_ |
121 | folly::RWTicketSpinLock32, |
122 | folly::RWTicketSpinLock64, |
123 | #endif |
124 | folly::SharedMutexReadPriority, |
125 | folly::SharedMutexWritePriority>; |
126 | TYPED_TEST_CASE(SynchronizedTimedTest, SynchronizedTimedTestTypes); |
127 | |
128 | TYPED_TEST(SynchronizedTimedTest, Timed) { |
129 | testTimed<TypeParam>(); |
130 | } |
131 | |
132 | TYPED_TEST(SynchronizedTimedTest, TimedSynchronized) { |
133 | testTimedSynchronized<TypeParam>(); |
134 | } |
135 | |
136 | template <class Mutex> |
137 | class SynchronizedTimedWithConstTest : public testing::Test {}; |
138 | |
139 | using SynchronizedTimedWithConstTestTypes = testing::Types< |
140 | #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES |
141 | boost::shared_mutex, |
142 | #endif |
143 | #ifdef RW_SPINLOCK_USE_X86_INTRINSIC_ |
144 | folly::RWTicketSpinLock32, |
145 | folly::RWTicketSpinLock64, |
146 | #endif |
147 | folly::SharedMutexReadPriority, |
148 | folly::SharedMutexWritePriority>; |
149 | TYPED_TEST_CASE( |
150 | SynchronizedTimedWithConstTest, |
151 | SynchronizedTimedWithConstTestTypes); |
152 | |
153 | TYPED_TEST(SynchronizedTimedWithConstTest, TimedShared) { |
154 | testTimedShared<TypeParam>(); |
155 | } |
156 | |
157 | TYPED_TEST(SynchronizedTimedWithConstTest, TimedSynchronizeWithConst) { |
158 | testTimedSynchronizedWithConst<TypeParam>(); |
159 | } |
160 | |
161 | using CountPair = std::pair<int, int>; |
162 | // This class is specialized only to be uesed in SynchronizedLockTest |
163 | class FakeMutex { |
164 | public: |
165 | void lock() { |
166 | ++lockCount_; |
167 | } |
168 | |
169 | void unlock() { |
170 | ++unlockCount_; |
171 | } |
172 | |
173 | static CountPair getLockUnlockCount() { |
174 | return CountPair{lockCount_, unlockCount_}; |
175 | } |
176 | |
177 | static void resetLockUnlockCount() { |
178 | lockCount_ = 0; |
179 | unlockCount_ = 0; |
180 | } |
181 | |
182 | private: |
183 | // Keep these two static for test access |
184 | // Keep them thread_local in case of tests are run in parallel within one |
185 | // process |
186 | static FOLLY_TLS int lockCount_; |
187 | static FOLLY_TLS int unlockCount_; |
188 | }; |
189 | FOLLY_TLS int FakeMutex::lockCount_{0}; |
190 | FOLLY_TLS int FakeMutex::unlockCount_{0}; |
191 | |
192 | // SynchronizedLockTest is used to verify the correct lock unlock behavior |
193 | // happens per design |
194 | class SynchronizedLockTest : public testing::Test { |
195 | public: |
196 | void SetUp() override { |
197 | FakeMutex::resetLockUnlockCount(); |
198 | } |
199 | }; |
200 | |
201 | /** |
202 | * Test mutex to help to automate assertions, taken from LockTraitsTest.cpp |
203 | */ |
204 | class FakeAllPowerfulAssertingMutexInternal { |
205 | public: |
206 | enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE }; |
207 | |
208 | void lock() { |
209 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
210 | this->lock_state = CurrentLockState::UNIQUE; |
211 | } |
212 | void unlock() { |
213 | EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); |
214 | this->lock_state = CurrentLockState::UNLOCKED; |
215 | } |
216 | void lock_shared() { |
217 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
218 | this->lock_state = CurrentLockState::SHARED; |
219 | } |
220 | void unlock_shared() { |
221 | EXPECT_EQ(this->lock_state, CurrentLockState::SHARED); |
222 | this->lock_state = CurrentLockState::UNLOCKED; |
223 | } |
224 | void lock_upgrade() { |
225 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
226 | this->lock_state = CurrentLockState::UPGRADE; |
227 | } |
228 | void unlock_upgrade() { |
229 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
230 | this->lock_state = CurrentLockState::UNLOCKED; |
231 | } |
232 | |
233 | void unlock_upgrade_and_lock() { |
234 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
235 | this->lock_state = CurrentLockState::UNIQUE; |
236 | } |
237 | void unlock_and_lock_upgrade() { |
238 | EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); |
239 | this->lock_state = CurrentLockState::UPGRADE; |
240 | } |
241 | void unlock_and_lock_shared() { |
242 | EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); |
243 | this->lock_state = CurrentLockState::SHARED; |
244 | } |
245 | void unlock_upgrade_and_lock_shared() { |
246 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
247 | this->lock_state = CurrentLockState::SHARED; |
248 | } |
249 | |
250 | template <class Rep, class Period> |
251 | bool try_lock_for(const std::chrono::duration<Rep, Period>&) { |
252 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
253 | this->lock_state = CurrentLockState::UNIQUE; |
254 | return true; |
255 | } |
256 | |
257 | template <class Rep, class Period> |
258 | bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) { |
259 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
260 | this->lock_state = CurrentLockState::UPGRADE; |
261 | return true; |
262 | } |
263 | |
264 | template <class Rep, class Period> |
265 | bool try_unlock_upgrade_and_lock_for( |
266 | const std::chrono::duration<Rep, Period>&) { |
267 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
268 | this->lock_state = CurrentLockState::UNIQUE; |
269 | return true; |
270 | } |
271 | |
272 | /* |
273 | * Initialize the FakeMutex with an unlocked state |
274 | */ |
275 | CurrentLockState lock_state{CurrentLockState::UNLOCKED}; |
276 | }; |
277 | |
278 | /** |
279 | * The following works around the internal mutex for synchronized being |
280 | * private |
281 | * |
282 | * This is horridly thread unsafe. |
283 | */ |
284 | static FakeAllPowerfulAssertingMutexInternal globalAllPowerfulAssertingMutex; |
285 | |
286 | class FakeAllPowerfulAssertingMutex { |
287 | public: |
288 | void lock() { |
289 | globalAllPowerfulAssertingMutex.lock(); |
290 | } |
291 | void unlock() { |
292 | globalAllPowerfulAssertingMutex.unlock(); |
293 | } |
294 | void lock_shared() { |
295 | globalAllPowerfulAssertingMutex.lock_shared(); |
296 | } |
297 | void unlock_shared() { |
298 | globalAllPowerfulAssertingMutex.unlock_shared(); |
299 | } |
300 | void lock_upgrade() { |
301 | globalAllPowerfulAssertingMutex.lock_upgrade(); |
302 | } |
303 | void unlock_upgrade() { |
304 | globalAllPowerfulAssertingMutex.unlock_upgrade(); |
305 | } |
306 | |
307 | void unlock_upgrade_and_lock() { |
308 | globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock(); |
309 | } |
310 | void unlock_and_lock_upgrade() { |
311 | globalAllPowerfulAssertingMutex.unlock_and_lock_upgrade(); |
312 | } |
313 | void unlock_and_lock_shared() { |
314 | globalAllPowerfulAssertingMutex.unlock_and_lock_shared(); |
315 | } |
316 | void unlock_upgrade_and_lock_shared() { |
317 | globalAllPowerfulAssertingMutex.unlock_upgrade_and_lock_shared(); |
318 | } |
319 | |
320 | template <class Rep, class Period> |
321 | bool try_lock_for(const std::chrono::duration<Rep, Period>& arg) { |
322 | return globalAllPowerfulAssertingMutex.try_lock_for(arg); |
323 | } |
324 | |
325 | template <class Rep, class Period> |
326 | bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>& arg) { |
327 | return globalAllPowerfulAssertingMutex.try_lock_upgrade_for(arg); |
328 | } |
329 | |
330 | template <class Rep, class Period> |
331 | bool try_unlock_upgrade_and_lock_for( |
332 | const std::chrono::duration<Rep, Period>& arg) { |
333 | return globalAllPowerfulAssertingMutex.try_unlock_upgrade_and_lock_for(arg); |
334 | } |
335 | |
336 | // reset state on destruction |
337 | ~FakeAllPowerfulAssertingMutex() { |
338 | globalAllPowerfulAssertingMutex = FakeAllPowerfulAssertingMutexInternal{}; |
339 | } |
340 | }; |
341 | |
342 | class NonDefaultConstructibleMutex { |
343 | public: |
344 | explicit NonDefaultConstructibleMutex(int valueIn) { |
345 | value = valueIn; |
346 | } |
347 | NonDefaultConstructibleMutex() = delete; |
348 | NonDefaultConstructibleMutex(const NonDefaultConstructibleMutex&) = delete; |
349 | NonDefaultConstructibleMutex(NonDefaultConstructibleMutex&&) = delete; |
350 | NonDefaultConstructibleMutex& operator=(const NonDefaultConstructibleMutex&) = |
351 | delete; |
352 | NonDefaultConstructibleMutex& operator=(NonDefaultConstructibleMutex&&) = |
353 | delete; |
354 | |
355 | static int value; |
356 | |
357 | void lock() {} |
358 | void unlock() {} |
359 | }; |
360 | int NonDefaultConstructibleMutex::value{0}; |
361 | |
362 | TEST_F(SynchronizedLockTest, TestCopyConstructibleValues) { |
363 | struct NonCopyConstructible { |
364 | NonCopyConstructible(const NonCopyConstructible&) = delete; |
365 | NonCopyConstructible& operator=(const NonCopyConstructible&) = delete; |
366 | }; |
367 | struct CopyConstructible {}; |
368 | EXPECT_FALSE(std::is_copy_constructible< |
369 | folly::Synchronized<NonCopyConstructible>>::value); |
370 | EXPECT_FALSE(std::is_copy_assignable< |
371 | folly::Synchronized<NonCopyConstructible>>::value); |
372 | EXPECT_TRUE(std::is_copy_constructible< |
373 | folly::Synchronized<CopyConstructible>>::value); |
374 | EXPECT_TRUE( |
375 | std::is_copy_assignable<folly::Synchronized<CopyConstructible>>::value); |
376 | } |
377 | |
378 | TEST_F(SynchronizedLockTest, UpgradeLocking) { |
379 | folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync; |
380 | |
381 | // sanity assert |
382 | static_assert( |
383 | std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value, |
384 | "The ulock function was not well configured, blame aary@instagram.com" ); |
385 | |
386 | { |
387 | auto ulock = sync.ulock(); |
388 | EXPECT_EQ( |
389 | globalAllPowerfulAssertingMutex.lock_state, |
390 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE); |
391 | } |
392 | |
393 | // should be unlocked here |
394 | EXPECT_EQ( |
395 | globalAllPowerfulAssertingMutex.lock_state, |
396 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
397 | |
398 | // test going from upgrade to exclusive |
399 | { |
400 | auto ulock = sync.ulock(); |
401 | auto wlock = ulock.moveFromUpgradeToWrite(); |
402 | EXPECT_EQ(static_cast<bool>(ulock), false); |
403 | EXPECT_EQ( |
404 | globalAllPowerfulAssertingMutex.lock_state, |
405 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE); |
406 | } |
407 | |
408 | // should be unlocked here |
409 | EXPECT_EQ( |
410 | globalAllPowerfulAssertingMutex.lock_state, |
411 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
412 | |
413 | // test going from upgrade to shared |
414 | { |
415 | auto ulock = sync.ulock(); |
416 | auto slock = ulock.moveFromUpgradeToRead(); |
417 | EXPECT_EQ(static_cast<bool>(ulock), false); |
418 | EXPECT_EQ( |
419 | globalAllPowerfulAssertingMutex.lock_state, |
420 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED); |
421 | } |
422 | |
423 | // should be unlocked here |
424 | EXPECT_EQ( |
425 | globalAllPowerfulAssertingMutex.lock_state, |
426 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
427 | |
428 | // test going from exclusive to upgrade |
429 | { |
430 | auto wlock = sync.wlock(); |
431 | auto ulock = wlock.moveFromWriteToUpgrade(); |
432 | EXPECT_EQ(static_cast<bool>(wlock), false); |
433 | EXPECT_EQ( |
434 | globalAllPowerfulAssertingMutex.lock_state, |
435 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE); |
436 | } |
437 | |
438 | // should be unlocked here |
439 | EXPECT_EQ( |
440 | globalAllPowerfulAssertingMutex.lock_state, |
441 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
442 | |
443 | // test going from exclusive to shared |
444 | { |
445 | auto wlock = sync.wlock(); |
446 | auto slock = wlock.moveFromWriteToRead(); |
447 | EXPECT_EQ(static_cast<bool>(wlock), false); |
448 | EXPECT_EQ( |
449 | globalAllPowerfulAssertingMutex.lock_state, |
450 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED); |
451 | } |
452 | |
453 | // should be unlocked here |
454 | EXPECT_EQ( |
455 | globalAllPowerfulAssertingMutex.lock_state, |
456 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
457 | } |
458 | |
459 | TEST_F(SynchronizedLockTest, UpgradeLockingWithULock) { |
460 | folly::Synchronized<int, FakeAllPowerfulAssertingMutex> sync; |
461 | |
462 | // sanity assert |
463 | static_assert( |
464 | std::is_same<std::decay<decltype(*sync.ulock())>::type, int>::value, |
465 | "The ulock function was not well configured, blame aary@instagram.com" ); |
466 | |
467 | // test from upgrade to write |
468 | sync.withULockPtr([](auto ulock) { |
469 | EXPECT_EQ(static_cast<bool>(ulock), true); |
470 | EXPECT_EQ( |
471 | globalAllPowerfulAssertingMutex.lock_state, |
472 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE); |
473 | |
474 | auto wlock = ulock.moveFromUpgradeToWrite(); |
475 | EXPECT_EQ(static_cast<bool>(ulock), false); |
476 | EXPECT_EQ( |
477 | globalAllPowerfulAssertingMutex.lock_state, |
478 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE); |
479 | }); |
480 | |
481 | // should be unlocked here |
482 | EXPECT_EQ( |
483 | globalAllPowerfulAssertingMutex.lock_state, |
484 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
485 | |
486 | // test from write to upgrade |
487 | sync.withWLockPtr([](auto wlock) { |
488 | EXPECT_EQ(static_cast<bool>(wlock), true); |
489 | EXPECT_EQ( |
490 | globalAllPowerfulAssertingMutex.lock_state, |
491 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE); |
492 | |
493 | auto ulock = wlock.moveFromWriteToUpgrade(); |
494 | EXPECT_EQ(static_cast<bool>(wlock), false); |
495 | EXPECT_EQ( |
496 | globalAllPowerfulAssertingMutex.lock_state, |
497 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE); |
498 | }); |
499 | |
500 | // should be unlocked here |
501 | EXPECT_EQ( |
502 | globalAllPowerfulAssertingMutex.lock_state, |
503 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
504 | |
505 | // test from upgrade to shared |
506 | sync.withULockPtr([](auto ulock) { |
507 | EXPECT_EQ(static_cast<bool>(ulock), true); |
508 | EXPECT_EQ( |
509 | globalAllPowerfulAssertingMutex.lock_state, |
510 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UPGRADE); |
511 | |
512 | auto slock = ulock.moveFromUpgradeToRead(); |
513 | EXPECT_EQ(static_cast<bool>(ulock), false); |
514 | EXPECT_EQ( |
515 | globalAllPowerfulAssertingMutex.lock_state, |
516 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED); |
517 | }); |
518 | |
519 | // should be unlocked here |
520 | EXPECT_EQ( |
521 | globalAllPowerfulAssertingMutex.lock_state, |
522 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
523 | |
524 | // test from write to shared |
525 | sync.withWLockPtr([](auto wlock) { |
526 | EXPECT_EQ(static_cast<bool>(wlock), true); |
527 | EXPECT_EQ( |
528 | globalAllPowerfulAssertingMutex.lock_state, |
529 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNIQUE); |
530 | |
531 | auto slock = wlock.moveFromWriteToRead(); |
532 | EXPECT_EQ(static_cast<bool>(wlock), false); |
533 | EXPECT_EQ( |
534 | globalAllPowerfulAssertingMutex.lock_state, |
535 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::SHARED); |
536 | }); |
537 | |
538 | // should be unlocked here |
539 | EXPECT_EQ( |
540 | globalAllPowerfulAssertingMutex.lock_state, |
541 | FakeAllPowerfulAssertingMutexInternal::CurrentLockState::UNLOCKED); |
542 | } |
543 | |
544 | TEST_F(SynchronizedLockTest, TestPieceWiseConstruct) { |
545 | auto&& synchronized = folly::Synchronized<int, NonDefaultConstructibleMutex>{ |
546 | std::piecewise_construct, |
547 | std::forward_as_tuple(3), |
548 | std::forward_as_tuple(1)}; |
549 | |
550 | EXPECT_EQ(*synchronized.lock(), 3); |
551 | EXPECT_EQ(NonDefaultConstructibleMutex::value, 1); |
552 | } |
553 | |
554 | namespace { |
555 | constexpr auto kLockable = 1; |
556 | constexpr auto kWLockable = 2; |
557 | constexpr auto kRLockable = 4; |
558 | constexpr auto kULockable = 8; |
559 | |
560 | template <int kLockableType> |
561 | class TryLockable { |
562 | public: |
563 | explicit TryLockable( |
564 | bool shouldSucceed, |
565 | folly::Function<void()> onLockIn, |
566 | folly::Function<void()> onUnlockIn) |
567 | : kShouldSucceed{shouldSucceed}, |
568 | onLock{std::move(onLockIn)}, |
569 | onUnlock{std::move(onUnlockIn)} {} |
570 | |
571 | void lock() { |
572 | EXPECT_TRUE(false); |
573 | } |
574 | template < |
575 | int LockableType = kLockableType, |
576 | std::enable_if_t<LockableType != kLockable>* = nullptr> |
577 | void lock_shared() { |
578 | EXPECT_TRUE(false); |
579 | } |
580 | template < |
581 | int LockableType = kLockableType, |
582 | std::enable_if_t<LockableType == kULockable>* = nullptr> |
583 | void lock_upgrade() { |
584 | EXPECT_TRUE(false); |
585 | } |
586 | |
587 | bool tryLockImpl(int lockableMask) { |
588 | // if the lockable type of this instance is one of the possible options as |
589 | // expressed in the mask go through the usual test code |
590 | if (kLockableType | lockableMask) { |
591 | if (kShouldSucceed) { |
592 | onLock(); |
593 | return true; |
594 | } else { |
595 | return false; |
596 | } |
597 | } |
598 | |
599 | // else fail the test |
600 | EXPECT_TRUE(false); |
601 | return false; |
602 | } |
603 | void unlockImpl(int lockableMask) { |
604 | if (kLockableType | lockableMask) { |
605 | onUnlock(); |
606 | return; |
607 | } |
608 | |
609 | EXPECT_TRUE(false); |
610 | } |
611 | |
612 | bool try_lock() { |
613 | return tryLockImpl(kLockable | kWLockable); |
614 | } |
615 | bool try_lock_shared() { |
616 | return tryLockImpl(kRLockable); |
617 | } |
618 | bool try_lock_upgrade() { |
619 | return tryLockImpl(kULockable); |
620 | } |
621 | |
622 | void unlock() { |
623 | unlockImpl(kLockable | kWLockable); |
624 | } |
625 | void unlock_shared() { |
626 | unlockImpl(kLockable | kRLockable); |
627 | } |
628 | void unlock_upgrade() { |
629 | unlockImpl(kLockable | kULockable); |
630 | } |
631 | |
632 | const bool kShouldSucceed; |
633 | folly::Function<void()> onLock; |
634 | folly::Function<void()> onUnlock; |
635 | }; |
636 | |
637 | struct TestSharedMutex { |
638 | public: |
639 | void lock() { |
640 | onLock_(); |
641 | } |
642 | void unlock() { |
643 | onUnlock_(); |
644 | } |
645 | void lock_shared() { |
646 | onLockShared_(); |
647 | } |
648 | void unlock_shared() { |
649 | onUnlockShared_(); |
650 | } |
651 | |
652 | bool try_lock() { |
653 | onLock_(); |
654 | return true; |
655 | } |
656 | bool try_lock_shared() { |
657 | onLockShared_(); |
658 | return true; |
659 | } |
660 | |
661 | std::function<void()> onLock_; |
662 | std::function<void()> onUnlock_; |
663 | std::function<void()> onLockShared_; |
664 | std::function<void()> onUnlockShared_; |
665 | }; |
666 | |
667 | struct TestMutex { |
668 | public: |
669 | void lock() { |
670 | onLock(); |
671 | ++numTimesLocked; |
672 | } |
673 | bool try_lock() { |
674 | if (shouldTryLockSucceed) { |
675 | lock(); |
676 | return true; |
677 | } |
678 | return false; |
679 | } |
680 | void unlock() { |
681 | onUnlock(); |
682 | ++numTimesUnlocked; |
683 | } |
684 | |
685 | int numTimesLocked{0}; |
686 | int numTimesUnlocked{0}; |
687 | bool shouldTryLockSucceed{true}; |
688 | std::function<void()> onLock{[] {}}; |
689 | std::function<void()> onUnlock{[] {}}; |
690 | }; |
691 | |
692 | template <int kLockable, typename Func> |
693 | void testTryLock(Func func) { |
694 | { |
695 | auto locked = 0; |
696 | auto unlocked = 0; |
697 | folly::Synchronized<int, TryLockable<kLockable>> synchronized{ |
698 | std::piecewise_construct, |
699 | std::make_tuple(), |
700 | std::make_tuple(true, [&] { ++locked; }, [&] { ++unlocked; })}; |
701 | |
702 | { |
703 | auto lock = func(synchronized); |
704 | EXPECT_TRUE(lock); |
705 | EXPECT_EQ(locked, 1); |
706 | } |
707 | EXPECT_EQ(locked, 1); |
708 | EXPECT_EQ(unlocked, 1); |
709 | } |
710 | { |
711 | auto locked = 0; |
712 | auto unlocked = 0; |
713 | folly::Synchronized<int, TryLockable<kLockable>> synchronized{ |
714 | std::piecewise_construct, |
715 | std::make_tuple(), |
716 | std::make_tuple(false, [&] { ++locked; }, [&] { ++unlocked; })}; |
717 | |
718 | { |
719 | auto lock = func(synchronized); |
720 | EXPECT_FALSE(lock); |
721 | EXPECT_EQ(locked, 0); |
722 | } |
723 | EXPECT_EQ(locked, 0); |
724 | EXPECT_EQ(unlocked, 0); |
725 | } |
726 | } |
727 | |
728 | class { |
729 | public: |
730 | static int ; |
731 | static int ; |
732 | |
733 | void () {} |
734 | void () {} |
735 | void () { |
736 | order = MutexTrack::gOrder++; |
737 | } |
738 | void () { |
739 | order = -1; |
740 | --gOrder; |
741 | } |
742 | |
743 | int {gId++}; |
744 | int {-1}; |
745 | }; |
746 | int MutexTrack::{0}; |
747 | int MutexTrack::{0}; |
748 | } // namespace |
749 | |
750 | TEST_F(SynchronizedLockTest, TestTryLock) { |
751 | testTryLock<kLockable>( |
752 | [](auto& synchronized) { return synchronized.tryLock(); }); |
753 | } |
754 | |
755 | TEST_F(SynchronizedLockTest, TestTryWLock) { |
756 | testTryLock<kWLockable>( |
757 | [](auto& synchronized) { return synchronized.tryWLock(); }); |
758 | } |
759 | |
760 | TEST_F(SynchronizedLockTest, TestTryRLock) { |
761 | testTryLock<kRLockable>( |
762 | [](auto& synchronized) { return synchronized.tryRLock(); }); |
763 | } |
764 | |
765 | TEST_F(SynchronizedLockTest, TestTryULock) { |
766 | testTryLock<kULockable>( |
767 | [](auto& synchronized) { return synchronized.tryULock(); }); |
768 | } |
769 | |
770 | template <typename LockPolicy> |
771 | using LPtr = LockedPtr<Synchronized<int>, LockPolicy>; |
772 | |
773 | namespace { |
774 | template <template <typename...> class Trait> |
775 | void testLockedPtrCompatibilityExclusive() { |
776 | EXPECT_TRUE(( |
777 | Trait<LPtr<LockPolicyExclusive>, LPtr<LockPolicyTryExclusive>&&>::value)); |
778 | EXPECT_TRUE((Trait< |
779 | LPtr<LockPolicyExclusive>, |
780 | LPtr<LockPolicyFromUpgradeToExclusive>&&>::value)); |
781 | |
782 | EXPECT_FALSE( |
783 | (Trait<LPtr<LockPolicyExclusive>&, LPtr<LockPolicyShared>&&>::value)); |
784 | EXPECT_FALSE( |
785 | (Trait<LPtr<LockPolicyExclusive>, LPtr<LockPolicyTryShared>&&>::value)); |
786 | EXPECT_FALSE( |
787 | (Trait<LPtr<LockPolicyExclusive>, LPtr<LockPolicyUpgrade>&&>::value)); |
788 | EXPECT_FALSE( |
789 | (Trait<LPtr<LockPolicyExclusive>, LPtr<LockPolicyTryUpgrade>&&>::value)); |
790 | EXPECT_FALSE((Trait< |
791 | LPtr<LockPolicyExclusive>, |
792 | LPtr<LockPolicyFromExclusiveToUpgrade>&&>::value)); |
793 | EXPECT_FALSE((Trait< |
794 | LPtr<LockPolicyExclusive>, |
795 | LPtr<LockPolicyFromExclusiveToShared>&&>::value)); |
796 | EXPECT_FALSE( |
797 | (Trait<LPtr<LockPolicyExclusive>, LPtr<LockPolicyFromUpgradeToShared>&&>:: |
798 | value)); |
799 | } |
800 | |
801 | template <template <typename...> class Trait> |
802 | void testLockedPtrCompatibilityShared() { |
803 | EXPECT_TRUE( |
804 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyTryShared>&&>::value)); |
805 | EXPECT_TRUE( |
806 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyFromUpgradeToShared>&&>:: |
807 | value)); |
808 | EXPECT_TRUE( |
809 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyFromExclusiveToShared>&&>:: |
810 | value)); |
811 | |
812 | EXPECT_FALSE( |
813 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyExclusive>&&>::value)); |
814 | EXPECT_FALSE( |
815 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyTryExclusive>&&>::value)); |
816 | EXPECT_FALSE( |
817 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyUpgrade>&&>::value)); |
818 | EXPECT_FALSE( |
819 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyTryUpgrade>&&>::value)); |
820 | EXPECT_FALSE( |
821 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyFromExclusiveToUpgrade>&&>:: |
822 | value)); |
823 | EXPECT_FALSE( |
824 | (Trait<LPtr<LockPolicyShared>, LPtr<LockPolicyFromUpgradeToExclusive>&&>:: |
825 | value)); |
826 | } |
827 | |
828 | template <template <typename...> class Trait> |
829 | void testLockedPtrCompatibilityUpgrade() { |
830 | EXPECT_TRUE( |
831 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyTryUpgrade>&&>::value)); |
832 | EXPECT_TRUE(( |
833 | Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyFromExclusiveToUpgrade>&&>:: |
834 | value)); |
835 | |
836 | EXPECT_FALSE( |
837 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyExclusive>&&>::value)); |
838 | EXPECT_FALSE( |
839 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyTryExclusive>&&>::value)); |
840 | EXPECT_FALSE( |
841 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyShared>&&>::value)); |
842 | EXPECT_FALSE( |
843 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyTryShared>&&>::value)); |
844 | EXPECT_FALSE( |
845 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyFromExclusiveToShared>&&>:: |
846 | value)); |
847 | EXPECT_FALSE( |
848 | (Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyFromUpgradeToShared>&&>:: |
849 | value)); |
850 | EXPECT_FALSE(( |
851 | Trait<LPtr<LockPolicyUpgrade>, LPtr<LockPolicyFromUpgradeToExclusive>&&>:: |
852 | value)); |
853 | } |
854 | } // namespace |
855 | |
856 | TEST_F(SynchronizedLockTest, TestLockedPtrCompatibilityExclusive) { |
857 | testLockedPtrCompatibilityExclusive<std::is_assignable>(); |
858 | testLockedPtrCompatibilityExclusive<std::is_constructible>(); |
859 | } |
860 | |
861 | TEST_F(SynchronizedLockTest, TestLockedPtrCompatibilityShared) { |
862 | testLockedPtrCompatibilityShared<std::is_assignable>(); |
863 | testLockedPtrCompatibilityShared<std::is_constructible>(); |
864 | } |
865 | |
866 | TEST_F(SynchronizedLockTest, TestLockedPtrCompatibilityUpgrade) { |
867 | testLockedPtrCompatibilityUpgrade<std::is_assignable>(); |
868 | testLockedPtrCompatibilityUpgrade<std::is_constructible>(); |
869 | } |
870 | |
871 | TEST_F(SynchronizedLockTest, TestConvertTryLockToLock) { |
872 | auto synchronized = folly::Synchronized<int>{0}; |
873 | auto wlock = synchronized.wlock(); |
874 | wlock.unlock(); |
875 | |
876 | auto ulock = synchronized.ulock(); |
877 | wlock = ulock.moveFromUpgradeToWrite(); |
878 | wlock.unlock(); |
879 | |
880 | auto value = synchronized.withWLock([](auto& integer) { return integer; }); |
881 | EXPECT_EQ(value, 0); |
882 | } |
883 | |
884 | TEST(FollyLockTest, TestVariadicLockWithSynchronized) { |
885 | { |
886 | auto syncs = std::array<folly::Synchronized<int>, 3>{}; |
887 | auto& one = syncs[0]; |
888 | auto const& two = syncs[1]; |
889 | auto& three = syncs[2]; |
890 | auto locks = |
891 | lock(folly::wlock(one), folly::rlock(two), folly::wlock(three)); |
892 | EXPECT_TRUE(std::get<0>(locks)); |
893 | EXPECT_TRUE(std::get<1>(locks)); |
894 | EXPECT_TRUE(std::get<2>(locks)); |
895 | } |
896 | { |
897 | auto syncs = std::array<folly::Synchronized<int, std::mutex>, 2>{}; |
898 | auto locks = lock(folly::lock(syncs[0]), folly::lock(syncs[1])); |
899 | EXPECT_TRUE(std::get<0>(locks)); |
900 | EXPECT_TRUE(std::get<1>(locks)); |
901 | } |
902 | } |
903 | |
904 | TEST(FollyLockTest, TestVariadicLockWithArbitraryLockables) { |
905 | auto&& one = std::mutex{}; |
906 | auto&& two = std::mutex{}; |
907 | |
908 | auto lckOne = std::unique_lock<std::mutex>{one, std::defer_lock}; |
909 | auto lckTwo = std::unique_lock<std::mutex>{two, std::defer_lock}; |
910 | folly::lock(lckOne, lckTwo); |
911 | EXPECT_TRUE(lckOne); |
912 | EXPECT_TRUE(lckTwo); |
913 | } |
914 | |
915 | TEST(FollyLockTest, TestVariadicLockSmartAndPoliteAlgorithm) { |
916 | auto one = TestMutex{}; |
917 | auto two = TestMutex{}; |
918 | auto three = TestMutex{}; |
919 | auto makeReset = [&] { |
920 | return folly::makeGuard([&] { |
921 | one = TestMutex{}; |
922 | two = TestMutex{}; |
923 | three = TestMutex{}; |
924 | }); |
925 | }; |
926 | |
927 | { |
928 | auto reset = makeReset(); |
929 | folly::lock(one, two, three); |
930 | EXPECT_EQ(one.numTimesLocked, 1); |
931 | EXPECT_EQ(one.numTimesUnlocked, 0); |
932 | EXPECT_EQ(two.numTimesLocked, 1); |
933 | EXPECT_EQ(two.numTimesUnlocked, 0); |
934 | EXPECT_EQ(three.numTimesLocked, 1); |
935 | EXPECT_EQ(three.numTimesUnlocked, 0); |
936 | } |
937 | |
938 | { |
939 | auto reset = makeReset(); |
940 | two.shouldTryLockSucceed = false; |
941 | folly::lock(one, two, three); |
942 | EXPECT_EQ(one.numTimesLocked, 2); |
943 | EXPECT_EQ(one.numTimesUnlocked, 1); |
944 | EXPECT_EQ(two.numTimesLocked, 1); |
945 | EXPECT_EQ(two.numTimesUnlocked, 0); |
946 | EXPECT_EQ(three.numTimesLocked, 1); |
947 | EXPECT_EQ(three.numTimesUnlocked, 0); |
948 | } |
949 | |
950 | { |
951 | auto reset = makeReset(); |
952 | three.shouldTryLockSucceed = false; |
953 | folly::lock(one, two, three); |
954 | EXPECT_EQ(one.numTimesLocked, 2); |
955 | EXPECT_EQ(one.numTimesUnlocked, 1); |
956 | EXPECT_EQ(two.numTimesLocked, 2); |
957 | EXPECT_EQ(two.numTimesUnlocked, 1); |
958 | EXPECT_EQ(three.numTimesLocked, 1); |
959 | EXPECT_EQ(three.numTimesUnlocked, 0); |
960 | } |
961 | |
962 | { |
963 | auto reset = makeReset(); |
964 | three.shouldTryLockSucceed = false; |
965 | |
966 | three.onLock = [&] { |
967 | // when three gets locked make one fail |
968 | one.shouldTryLockSucceed = false; |
969 | // then when one gets locked make three succeed to finish the test |
970 | one.onLock = [&] { three.shouldTryLockSucceed = true; }; |
971 | }; |
972 | |
973 | folly::lock(one, two, three); |
974 | EXPECT_EQ(one.numTimesLocked, 2); |
975 | EXPECT_EQ(one.numTimesUnlocked, 1); |
976 | EXPECT_EQ(two.numTimesLocked, 2); |
977 | EXPECT_EQ(two.numTimesUnlocked, 1); |
978 | EXPECT_EQ(three.numTimesLocked, 2); |
979 | EXPECT_EQ(three.numTimesUnlocked, 1); |
980 | } |
981 | } |
982 | |
983 | TEST(SynchronizedAlgorithmTest, Basic) { |
984 | auto sync = Synchronized<int>{0}; |
985 | auto value = synchronized([](auto s) { return *s; }, wlock(sync)); |
986 | EXPECT_EQ(value, 0); |
987 | } |
988 | |
989 | TEST(SynchronizedAlgorithmTest, BasicNonShareableMutex) { |
990 | auto sync = Synchronized<int, std::mutex>{0}; |
991 | auto value = synchronized([](auto s) { return *s; }, lock(sync)); |
992 | EXPECT_EQ(value, 0); |
993 | } |
994 | |
995 | TEST(Synchronized, SynchronizedFunctionNonConst) { |
996 | auto locked = 0; |
997 | auto unlocked = 0; |
998 | auto sync = Synchronized<int, TestSharedMutex>{ |
999 | std::piecewise_construct, |
1000 | std::make_tuple(0), |
1001 | std::make_tuple([&] { ++locked; }, [&] { ++unlocked; }, [] {}, [] {})}; |
1002 | |
1003 | synchronized([](auto) {}, wlock(sync)); |
1004 | EXPECT_EQ(locked, 1); |
1005 | EXPECT_EQ(unlocked, 1); |
1006 | } |
1007 | |
1008 | TEST(Synchronized, SynchronizedFunctionConst) { |
1009 | auto locked = 0; |
1010 | auto unlocked = 0; |
1011 | auto sync = Synchronized<int, TestSharedMutex>{ |
1012 | std::piecewise_construct, |
1013 | std::make_tuple(0), |
1014 | std::make_tuple([] {}, [] {}, [&] { ++locked; }, [&] { ++unlocked; })}; |
1015 | |
1016 | synchronized([](auto) {}, rlock(sync)); |
1017 | EXPECT_EQ(locked, 1); |
1018 | EXPECT_EQ(unlocked, 1); |
1019 | } |
1020 | |
1021 | TEST(Synchronized, SynchronizedFunctionManyObjects) { |
1022 | auto fail = [] { EXPECT_TRUE(false); }; |
1023 | auto pass = [] {}; |
1024 | |
1025 | auto one = Synchronized<int, TestSharedMutex>{ |
1026 | std::piecewise_construct, |
1027 | std::make_tuple(0), |
1028 | std::make_tuple(pass, pass, fail, fail)}; |
1029 | auto two = Synchronized<std::string, TestSharedMutex>{ |
1030 | std::piecewise_construct, |
1031 | std::make_tuple(), |
1032 | std::make_tuple(fail, fail, pass, pass)}; |
1033 | |
1034 | synchronized([](auto, auto) {}, wlock(one), rlock(two)); |
1035 | } |
1036 | |
1037 | } // namespace folly |
1038 | |