1 | /* |
2 | * Copyright 2016-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 | #include <folly/LockTraits.h> |
17 | #include <folly/LockTraitsBoost.h> |
18 | |
19 | #include <mutex> |
20 | |
21 | #include <folly/SharedMutex.h> |
22 | #include <folly/SpinLock.h> |
23 | #include <folly/portability/GTest.h> |
24 | #include <folly/synchronization/RWSpinLock.h> |
25 | |
26 | using namespace folly; |
27 | |
28 | static constexpr auto one_ms = std::chrono::milliseconds(1); |
29 | |
30 | /** |
31 | * Test mutex to help to automate assertions |
32 | */ |
33 | class FakeAllPowerfulAssertingMutex { |
34 | public: |
35 | enum class CurrentLockState { UNLOCKED, SHARED, UPGRADE, UNIQUE }; |
36 | |
37 | void lock() { |
38 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
39 | this->lock_state = CurrentLockState::UNIQUE; |
40 | } |
41 | void unlock() { |
42 | EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); |
43 | this->lock_state = CurrentLockState::UNLOCKED; |
44 | } |
45 | void lock_shared() { |
46 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
47 | this->lock_state = CurrentLockState::SHARED; |
48 | } |
49 | void unlock_shared() { |
50 | EXPECT_EQ(this->lock_state, CurrentLockState::SHARED); |
51 | this->lock_state = CurrentLockState::UNLOCKED; |
52 | } |
53 | void lock_upgrade() { |
54 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
55 | this->lock_state = CurrentLockState::UPGRADE; |
56 | } |
57 | void unlock_upgrade() { |
58 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
59 | this->lock_state = CurrentLockState::UNLOCKED; |
60 | } |
61 | |
62 | void unlock_upgrade_and_lock() { |
63 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
64 | this->lock_state = CurrentLockState::UNIQUE; |
65 | } |
66 | void unlock_and_lock_upgrade() { |
67 | EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); |
68 | this->lock_state = CurrentLockState::UPGRADE; |
69 | } |
70 | void unlock_and_lock_shared() { |
71 | EXPECT_EQ(this->lock_state, CurrentLockState::UNIQUE); |
72 | this->lock_state = CurrentLockState::SHARED; |
73 | } |
74 | void unlock_upgrade_and_lock_shared() { |
75 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
76 | this->lock_state = CurrentLockState::SHARED; |
77 | } |
78 | |
79 | template <class Rep, class Period> |
80 | bool try_lock_for(const std::chrono::duration<Rep, Period>&) { |
81 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
82 | this->lock_state = CurrentLockState::UNIQUE; |
83 | return true; |
84 | } |
85 | |
86 | template <class Rep, class Period> |
87 | bool try_lock_upgrade_for(const std::chrono::duration<Rep, Period>&) { |
88 | EXPECT_EQ(this->lock_state, CurrentLockState::UNLOCKED); |
89 | this->lock_state = CurrentLockState::UPGRADE; |
90 | return true; |
91 | } |
92 | |
93 | template <class Rep, class Period> |
94 | bool try_unlock_upgrade_and_lock_for( |
95 | const std::chrono::duration<Rep, Period>&) { |
96 | EXPECT_EQ(this->lock_state, CurrentLockState::UPGRADE); |
97 | this->lock_state = CurrentLockState::UNIQUE; |
98 | return true; |
99 | } |
100 | |
101 | /* |
102 | * Initialize the FakeMutex with an unlocked state |
103 | */ |
104 | CurrentLockState lock_state{CurrentLockState::UNLOCKED}; |
105 | }; |
106 | |
107 | TEST(LockTraits, std_mutex) { |
108 | using traits = LockTraits<std::mutex>; |
109 | static_assert(!traits::is_timed, "std:mutex is not a timed lock" ); |
110 | static_assert(!traits::is_shared, "std:mutex is not a shared lock" ); |
111 | static_assert(!traits::is_upgrade, "std::mutex is not an upgradable lock" ); |
112 | |
113 | std::mutex mutex; |
114 | traits::lock(mutex); |
115 | traits::unlock(mutex); |
116 | } |
117 | |
118 | TEST(LockTraits, SharedMutex) { |
119 | using traits = LockTraits<SharedMutex>; |
120 | static_assert(traits::is_timed, "folly::SharedMutex is a timed lock" ); |
121 | static_assert(traits::is_shared, "folly::SharedMutex is a shared lock" ); |
122 | static_assert(traits::is_upgrade, "folly::SharedMutex is an upgradable lock" ); |
123 | |
124 | SharedMutex mutex; |
125 | traits::lock(mutex); |
126 | traits::unlock(mutex); |
127 | |
128 | traits::lock_shared(mutex); |
129 | traits::lock_shared(mutex); |
130 | traits::unlock_shared(mutex); |
131 | traits::unlock_shared(mutex); |
132 | |
133 | traits::lock_upgrade(mutex); |
134 | traits::unlock_upgrade(mutex); |
135 | |
136 | // test upgrade and downgrades |
137 | traits::lock_upgrade(mutex); |
138 | traits::unlock_upgrade_and_lock(mutex); |
139 | bool gotLock = traits::try_lock_for(mutex, one_ms); |
140 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an exclusive " |
141 | "lock after upgrading to an exclusive lock" ; |
142 | gotLock = traits::try_lock_upgrade_for(mutex, one_ms); |
143 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an upgrade " |
144 | "lock after upgrading to an exclusive lock" ; |
145 | gotLock = traits::try_lock_shared_for(mutex, one_ms); |
146 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire a shared " |
147 | "lock after upgrading to an exclusive lock" ; |
148 | traits::unlock(mutex); |
149 | |
150 | traits::lock_upgrade(mutex); |
151 | traits::unlock_upgrade_and_lock_shared(mutex); |
152 | gotLock = traits::try_lock_for(mutex, one_ms); |
153 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an exclusive " |
154 | "mutex after downgrading from an upgrade to a " |
155 | "shared lock" ; |
156 | traits::unlock_shared(mutex); |
157 | |
158 | traits::lock(mutex); |
159 | gotLock = traits::try_lock_for(mutex, one_ms); |
160 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an exclusive " |
161 | "lock after acquiring an exclusive lock" ; |
162 | gotLock = traits::try_lock_upgrade_for(mutex, one_ms); |
163 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an upgrade " |
164 | "lock after acquiring an exclusive lock" ; |
165 | gotLock = traits::try_lock_shared_for(mutex, one_ms); |
166 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire a shared " |
167 | "lock after acquiring an exclusive lock" ; |
168 | traits::unlock_and_lock_upgrade(mutex); |
169 | gotLock = traits::try_lock_for(mutex, one_ms); |
170 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an exclusive " |
171 | "lock after downgrading to an upgrade lock" ; |
172 | traits::unlock_upgrade(mutex); |
173 | |
174 | traits::lock(mutex); |
175 | traits::unlock_and_lock_shared(mutex); |
176 | gotLock = traits::try_lock_for(mutex, one_ms); |
177 | EXPECT_FALSE(gotLock) << "Should not have been able to acquire an exclusive " |
178 | "lock after downgrading to a shared lock" ; |
179 | traits::unlock_shared(mutex); |
180 | } |
181 | |
182 | TEST(LockTraits, SpinLock) { |
183 | using traits = LockTraits<SpinLock>; |
184 | static_assert(!traits::is_timed, "folly::SpinLock is not a timed lock" ); |
185 | static_assert(!traits::is_shared, "folly::SpinLock is not a shared lock" ); |
186 | static_assert( |
187 | !traits::is_upgrade, "folly::SpinLock is not an upgradable lock" ); |
188 | |
189 | SpinLock mutex; |
190 | traits::lock(mutex); |
191 | traits::unlock(mutex); |
192 | } |
193 | |
194 | TEST(LockTraits, RWSpinLock) { |
195 | using traits = LockTraits<RWSpinLock>; |
196 | static_assert(!traits::is_timed, "folly::RWSpinLock is not a timed lock" ); |
197 | static_assert(traits::is_shared, "folly::RWSpinLock is a shared lock" ); |
198 | static_assert(traits::is_upgrade, "folly::RWSpinLock is an upgradable lock" ); |
199 | |
200 | RWSpinLock mutex; |
201 | traits::lock(mutex); |
202 | traits::unlock(mutex); |
203 | |
204 | traits::lock_shared(mutex); |
205 | traits::lock_shared(mutex); |
206 | traits::unlock_shared(mutex); |
207 | traits::unlock_shared(mutex); |
208 | } |
209 | |
210 | TEST(LockTraits, boost_mutex) { |
211 | using traits = LockTraits<boost::mutex>; |
212 | static_assert(!traits::is_timed, "boost::mutex is not a timed lock" ); |
213 | static_assert(!traits::is_shared, "boost::mutex is not a shared lock" ); |
214 | static_assert(!traits::is_upgrade, "boost::mutex is not an upgradable lock" ); |
215 | |
216 | boost::mutex mutex; |
217 | traits::lock(mutex); |
218 | traits::unlock(mutex); |
219 | } |
220 | |
221 | TEST(LockTraits, boost_recursive_mutex) { |
222 | using traits = LockTraits<boost::recursive_mutex>; |
223 | static_assert( |
224 | !traits::is_timed, "boost::recursive_mutex is not a timed lock" ); |
225 | static_assert( |
226 | !traits::is_shared, "boost::recursive_mutex is not a shared lock" ); |
227 | static_assert( |
228 | !traits::is_upgrade, "boost::recursive_mutex is not an upgradable lock" ); |
229 | |
230 | boost::recursive_mutex mutex; |
231 | traits::lock(mutex); |
232 | traits::lock(mutex); |
233 | traits::unlock(mutex); |
234 | traits::unlock(mutex); |
235 | } |
236 | |
237 | #if FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES |
238 | TEST(LockTraits, timed_mutex) { |
239 | using traits = LockTraits<std::timed_mutex>; |
240 | static_assert(traits::is_timed, "std::timed_mutex is a timed lock" ); |
241 | static_assert(!traits::is_shared, "std::timed_mutex is not a shared lock" ); |
242 | static_assert( |
243 | !traits::is_upgrade, "std::timed_mutex is not an upgradable lock" ); |
244 | |
245 | std::timed_mutex mutex; |
246 | traits::lock(mutex); |
247 | bool gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); |
248 | EXPECT_FALSE(gotLock) << "should not have been able to acquire the " |
249 | << "timed_mutex a second time" ; |
250 | traits::unlock(mutex); |
251 | } |
252 | |
253 | TEST(LockTraits, recursive_timed_mutex) { |
254 | using traits = LockTraits<std::recursive_timed_mutex>; |
255 | static_assert(traits::is_timed, "std::recursive_timed_mutex is a timed lock" ); |
256 | static_assert( |
257 | !traits::is_shared, "std::recursive_timed_mutex is not a shared lock" ); |
258 | static_assert( |
259 | !traits::is_upgrade, |
260 | "std::recursive_timed_mutex is not an upgradable lock" ); |
261 | |
262 | std::recursive_timed_mutex mutex; |
263 | traits::lock(mutex); |
264 | auto gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(10)); |
265 | EXPECT_TRUE(gotLock) << "should have been able to acquire the " |
266 | << "recursive_timed_mutex a second time" ; |
267 | traits::unlock(mutex); |
268 | traits::unlock(mutex); |
269 | } |
270 | |
271 | TEST(LockTraits, boost_shared_mutex) { |
272 | using traits = LockTraits<boost::shared_mutex>; |
273 | static_assert(traits::is_timed, "boost::shared_mutex is a timed lock" ); |
274 | static_assert(traits::is_shared, "boost::shared_mutex is a shared lock" ); |
275 | static_assert( |
276 | traits::is_upgrade, "boost::shared_mutex is an upgradable lock" ); |
277 | |
278 | boost::shared_mutex mutex; |
279 | traits::lock(mutex); |
280 | auto gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); |
281 | EXPECT_FALSE(gotLock) << "should not have been able to acquire the " |
282 | << "shared_mutex a second time" ; |
283 | gotLock = traits::try_lock_shared_for(mutex, std::chrono::milliseconds(1)); |
284 | EXPECT_FALSE(gotLock) << "should not have been able to acquire the " |
285 | << "shared_mutex a second time" ; |
286 | traits::unlock(mutex); |
287 | |
288 | traits::lock_shared(mutex); |
289 | gotLock = traits::try_lock_for(mutex, std::chrono::milliseconds(1)); |
290 | EXPECT_FALSE(gotLock) << "should not have been able to acquire the " |
291 | << "shared_mutex a second time" ; |
292 | gotLock = traits::try_lock_shared_for(mutex, std::chrono::milliseconds(10)); |
293 | EXPECT_TRUE(gotLock) << "should have been able to acquire the " |
294 | << "shared_mutex a second time in shared mode" ; |
295 | traits::unlock_shared(mutex); |
296 | traits::unlock_shared(mutex); |
297 | } |
298 | #endif // FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES |
299 | |
300 | /** |
301 | * Chain the asserts from the previous test to the next lock, unlock or |
302 | * upgrade method calls. Each making sure that the previous was correct. |
303 | */ |
304 | TEST(LockTraits, LockPolicy) { |
305 | using Mutex = FakeAllPowerfulAssertingMutex; |
306 | Mutex mutex; |
307 | |
308 | // test the lock and unlock functions |
309 | LockPolicyUpgrade::lock(mutex); |
310 | mutex.unlock_upgrade(); |
311 | mutex.lock_upgrade(); |
312 | LockPolicyUpgrade::unlock(mutex); |
313 | |
314 | mutex.lock_upgrade(); |
315 | LockPolicyFromUpgradeToExclusive::lock(mutex); |
316 | mutex.unlock(); |
317 | mutex.lock(); |
318 | LockPolicyFromUpgradeToExclusive::unlock(mutex); |
319 | |
320 | mutex.lock(); |
321 | LockPolicyFromExclusiveToUpgrade::lock(mutex); |
322 | mutex.unlock_upgrade(); |
323 | mutex.lock_upgrade(); |
324 | LockPolicyFromExclusiveToUpgrade::unlock(mutex); |
325 | |
326 | mutex.lock_upgrade(); |
327 | LockPolicyFromUpgradeToShared::lock(mutex); |
328 | mutex.unlock_shared(); |
329 | mutex.lock_shared(); |
330 | LockPolicyFromUpgradeToShared::unlock(mutex); |
331 | |
332 | mutex.lock(); |
333 | LockPolicyFromExclusiveToShared::lock(mutex); |
334 | mutex.unlock_shared(); |
335 | mutex.lock_shared(); |
336 | LockPolicyFromExclusiveToShared::unlock(mutex); |
337 | |
338 | EXPECT_EQ(mutex.lock_state, Mutex::CurrentLockState::UNLOCKED); |
339 | } |
340 | |
341 | /** |
342 | * Similar to the test above but tests the timed version of the updates |
343 | */ |
344 | TEST(LockTraits, LockPolicyTimed) { |
345 | using Mutex = FakeAllPowerfulAssertingMutex; |
346 | Mutex mutex; |
347 | |
348 | bool gotLock = LockPolicyUpgrade::try_lock_for(mutex, one_ms); |
349 | EXPECT_TRUE(gotLock) << "Should have been able to acquire the fake mutex" ; |
350 | LockPolicyUpgrade::unlock(mutex); |
351 | |
352 | mutex.lock_upgrade(); |
353 | gotLock = LockPolicyFromUpgradeToExclusive::try_lock_for(mutex, one_ms); |
354 | EXPECT_TRUE(gotLock) |
355 | << "Should have been able to upgrade from upgrade to unique" ; |
356 | mutex.unlock(); |
357 | |
358 | mutex.lock(); |
359 | gotLock = LockPolicyFromExclusiveToUpgrade::try_lock_for(mutex, one_ms); |
360 | EXPECT_TRUE(gotLock) << "Should have been able to downgrade from exclusive " |
361 | "to upgrade" ; |
362 | mutex.unlock_upgrade(); |
363 | |
364 | mutex.lock_upgrade(); |
365 | gotLock = LockPolicyFromUpgradeToShared::try_lock_for(mutex, one_ms); |
366 | EXPECT_TRUE(gotLock) << "Should have been able to downgrade from upgrade to " |
367 | "shared" ; |
368 | mutex.unlock_shared(); |
369 | |
370 | mutex.lock(); |
371 | gotLock = LockPolicyFromExclusiveToShared::try_lock_for(mutex, one_ms); |
372 | EXPECT_TRUE(gotLock) << "Should have been able to downgrade from exclusive " |
373 | "to shared" ; |
374 | mutex.unlock_shared(); |
375 | } |
376 | |
377 | /** |
378 | * Test compatibility of the different lock policies |
379 | * |
380 | * This should be correct because the compatibilities here are used to |
381 | * determine whether or not the different LockedPtr instances can be moved |
382 | * from each other |
383 | */ |
384 | TEST(LockTraits, LockPolicyCompatibilities) { |
385 | EXPECT_TRUE((std::is_same< |
386 | LockPolicyExclusive::UnlockPolicy, |
387 | LockPolicyTryExclusive::UnlockPolicy>::value)); |
388 | EXPECT_TRUE((std::is_same< |
389 | LockPolicyExclusive::UnlockPolicy, |
390 | LockPolicyFromUpgradeToExclusive::UnlockPolicy>::value)); |
391 | |
392 | EXPECT_TRUE((std::is_same< |
393 | LockPolicyShared::UnlockPolicy, |
394 | LockPolicyTryShared::UnlockPolicy>::value)); |
395 | EXPECT_TRUE((std::is_same< |
396 | LockPolicyShared::UnlockPolicy, |
397 | LockPolicyFromUpgradeToShared::UnlockPolicy>::value)); |
398 | EXPECT_TRUE((std::is_same< |
399 | LockPolicyShared::UnlockPolicy, |
400 | LockPolicyFromExclusiveToShared::UnlockPolicy>::value)); |
401 | |
402 | EXPECT_TRUE((std::is_same< |
403 | LockPolicyUpgrade::UnlockPolicy, |
404 | LockPolicyTryUpgrade::UnlockPolicy>::value)); |
405 | EXPECT_TRUE((std::is_same< |
406 | LockPolicyUpgrade::UnlockPolicy, |
407 | LockPolicyFromExclusiveToUpgrade::UnlockPolicy>::value)); |
408 | } |
409 | |