2 * Copyright 2017 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.
18 * This module provides a traits class for describing properties about mutex
21 * This is a primitive for building higher-level abstractions that can work
22 * with a variety of mutex classes. For instance, this allows
23 * folly::Synchronized to support a number of different mutex types.
28 #include <type_traits>
30 // Android, OSX, and Cygwin don't have timed mutexes
31 #if defined(ANDROID) || defined(__ANDROID__) || defined(__APPLE__) || \
33 #define FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES 0
35 #define FOLLY_LOCK_TRAITS_HAVE_TIMED_MUTEXES 1
42 * An enum to describe the "level" of a mutex. The supported levels are
43 * Unique - a normal mutex that supports only exclusive locking
44 * Shared - a shared mutex which has shared locking and unlocking functions;
45 * Upgrade - a mutex that has all the methods of the two above along with
46 * support for upgradable locking
48 enum class MutexLevel { UNIQUE, SHARED, UPGRADE };
51 * A template dispatch mechanism that is used to determine the level of the
52 * mutex based on its interface. As decided by LockInterfaceDispatcher.
54 template <bool is_unique, bool is_shared, bool is_upgrade>
55 struct MutexLevelValueImpl;
57 struct MutexLevelValueImpl<true, false, false> {
58 static constexpr MutexLevel value = MutexLevel::UNIQUE;
61 struct MutexLevelValueImpl<true, true, false> {
62 static constexpr MutexLevel value = MutexLevel::SHARED;
65 struct MutexLevelValueImpl<true, true, true> {
66 static constexpr MutexLevel value = MutexLevel::UPGRADE;
70 * An internal helper class to help identify the interface supported by the
71 * mutex. This is used in conjunction with the above MutexLevel
72 * specializations and the LockTraitsImpl to determine what functions are
73 * supported by objects of type Mutex
75 * The implementation uses SINAE in the return value with trailing return
76 * types to figure out what level a mutex is
78 template <class Mutex>
79 class LockInterfaceDispatcher {
81 // assert that the mutex type has basic lock and unlock functions
83 std::is_same<decltype(std::declval<Mutex>().lock()), void>::value,
84 "The mutex type must support lock and unlock functions");
86 // Helper functions for implementing the traits using SFINAE
88 static auto timed_lock_test(T*) -> typename std::is_same<
89 decltype(std::declval<T>().try_lock_for(std::chrono::milliseconds(0))),
92 static std::false_type timed_lock_test(...);
95 static auto lock_shared_test(T*) -> typename std::
96 is_same<decltype(std::declval<T>().lock_shared()), void>::type;
98 static std::false_type lock_shared_test(...);
101 static auto lock_upgrade_test(T*) -> typename std::
102 is_same<decltype(std::declval<T>().lock_upgrade()), void>::type;
104 static std::false_type lock_upgrade_test(...);
107 static constexpr bool has_lock_unique = true;
108 static constexpr bool has_lock_timed =
109 decltype(timed_lock_test<Mutex>(0))::value;
110 static constexpr bool has_lock_shared =
111 decltype(lock_shared_test<Mutex>(0))::value;
112 static constexpr bool has_lock_upgrade =
113 decltype(lock_upgrade_test<Mutex>(0))::value;
117 * LockTraitsImpl is the base that is used to desribe the interface used by
118 * different mutex types. It accepts a MutexLevel argument and a boolean to
119 * show whether the mutex is a timed mutex or not. The implementations are
120 * partially specialized and inherit from the other implementations to get
121 * similar functionality
123 template <class Mutex, MutexLevel level, bool is_timed>
124 struct LockTraitsImpl;
126 template <class Mutex>
127 struct LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
128 static constexpr bool is_timed{false};
129 static constexpr bool is_shared{false};
130 static constexpr bool is_upgrade{false};
133 * Acquire the lock exclusively.
135 static void lock(Mutex& mutex) {
140 * Release an exclusively-held lock.
142 static void unlock(Mutex& mutex) {
148 * Higher level mutexes have all the capabilities of the lower levels so
151 template <class Mutex>
152 struct LockTraitsImpl<Mutex, MutexLevel::SHARED, false>
153 : public LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
154 static constexpr bool is_timed{false};
155 static constexpr bool is_shared{true};
156 static constexpr bool is_upgrade{false};
159 * Acquire the lock in shared (read) mode.
161 static void lock_shared(Mutex& mutex) {
166 * Release a lock held in shared mode.
168 static void unlock_shared(Mutex& mutex) {
169 mutex.unlock_shared();
174 * The following methods are supported. There are a few methods
179 * m.unlock_upgrade_and_lock()
181 * m.unlock_and_lock_upgrade()
182 * m.unlock_and_lock_shared()
183 * m.unlock_upgrade_and_lock_shared()
185 * m.try_lock_upgrade_for(rel_time)
186 * m.try_unlock_upgrade_and_lock_for(rel_time)
188 * Upgrading a shared lock is likely to deadlock when there is more than one
189 * thread performing an upgrade. This applies both to upgrading a shared lock
190 * to an upgrade lock and to upgrading a shared lock to a unique lock.
192 * Therefore, none of the following methods is supported:
193 * unlock_shared_and_lock_upgrade
194 * unlock_shared_and_lock
195 * try_unlock_shared_and_lock_upgrade
196 * try_unlock_shared_and_lock
197 * try_unlock_shared_and_lock_upgrade_for
198 * try_unlock_shared_and_lock_for
200 template <class Mutex>
201 struct LockTraitsImpl<Mutex, MutexLevel::UPGRADE, false>
202 : public LockTraitsImpl<Mutex, MutexLevel::SHARED, false> {
203 static constexpr bool is_timed{false};
204 static constexpr bool is_shared{true};
205 static constexpr bool is_upgrade{true};
208 * Acquire the lock in upgradable mode.
210 static void lock_upgrade(Mutex& mutex) {
211 mutex.lock_upgrade();
215 * Release the lock in upgrade mode
217 static void unlock_upgrade(Mutex& mutex) {
218 mutex.unlock_upgrade();
222 * Upgrade from an upgradable state to an exclusive state
224 static void unlock_upgrade_and_lock(Mutex& mutex) {
225 mutex.unlock_upgrade_and_lock();
229 * Downgrade from an exclusive state to an upgrade state
231 static void unlock_and_lock_upgrade(Mutex& mutex) {
232 mutex.unlock_and_lock_upgrade();
236 * Downgrade from an exclusive state to a shared state
238 static void unlock_and_lock_shared(Mutex& mutex) {
239 mutex.unlock_and_lock_shared();
243 * Downgrade from an upgrade state to a shared state
245 static void unlock_upgrade_and_lock_shared(Mutex& mutex) {
246 mutex.unlock_upgrade_and_lock_shared();
250 template <class Mutex>
251 struct LockTraitsImpl<Mutex, MutexLevel::UNIQUE, true>
252 : public LockTraitsImpl<Mutex, MutexLevel::UNIQUE, false> {
253 static constexpr bool is_timed{true};
254 static constexpr bool is_shared{false};
255 static constexpr bool is_upgrade{false};
258 * Acquire the lock exclusively, with a timeout.
260 * Returns true or false indicating if the lock was acquired or not.
262 template <class Rep, class Period>
263 static bool try_lock_for(
265 const std::chrono::duration<Rep, Period>& timeout) {
266 return mutex.try_lock_for(timeout);
271 * Note that there is no deadly diamond here because all the structs only have
272 * static functions and static bools which are going to be overridden by the
273 * lowest level implementation
275 template <class Mutex>
276 struct LockTraitsImpl<Mutex, MutexLevel::SHARED, true>
277 : public LockTraitsImpl<Mutex, MutexLevel::SHARED, false>,
278 public LockTraitsImpl<Mutex, MutexLevel::UNIQUE, true> {
279 static constexpr bool is_timed{true};
280 static constexpr bool is_shared{true};
281 static constexpr bool is_upgrade{false};
284 * Acquire the lock exclusively, with a timeout.
286 * Returns true or false indicating if the lock was acquired or not.
288 template <class Rep, class Period>
289 static bool try_lock_for(
291 const std::chrono::duration<Rep, Period>& timeout) {
292 return mutex.try_lock_for(timeout);
296 * Acquire the lock in shared (read) mode, with a timeout.
298 * Returns true or false indicating if the lock was acquired or not.
300 template <class Rep, class Period>
301 static bool try_lock_shared_for(
303 const std::chrono::duration<Rep, Period>& timeout) {
304 return mutex.try_lock_shared_for(timeout);
308 template <class Mutex>
309 struct LockTraitsImpl<Mutex, MutexLevel::UPGRADE, true>
310 : public LockTraitsImpl<Mutex, MutexLevel::UPGRADE, false>,
311 public LockTraitsImpl<Mutex, MutexLevel::SHARED, true> {
312 static constexpr bool is_timed{true};
313 static constexpr bool is_shared{true};
314 static constexpr bool is_upgrade{true};
317 * Acquire the lock in upgrade mode with a timeout
319 * Returns true or false indicating whether the lock was acquired or not
321 template <class Rep, class Period>
322 static bool try_lock_upgrade_for(
324 const std::chrono::duration<Rep, Period>& timeout) {
325 return mutex.try_lock_upgrade_for(timeout);
329 * Try to upgrade from an upgradable state to an exclusive state.
331 * Returns true or false indicating whether the lock was acquired or not
333 template <class Rep, class Period>
334 static bool try_unlock_upgrade_and_lock_for(
336 const std::chrono::duration<Rep, Period>& timeout) {
337 return mutex.try_unlock_upgrade_and_lock_for(timeout);
344 * LockTraits describes details about a particular mutex type.
346 * The default implementation automatically attempts to detect traits
347 * based on the presence of various member functions.
349 * You can specialize LockTraits to provide custom behavior for lock
350 * classes that do not use the standard method names
351 * (lock()/unlock()/lock_shared()/unlock_shared()/try_lock_for())
354 * LockTraits contains the following members variables:
355 * - static constexpr bool is_shared
356 * True if the lock supports separate shared vs exclusive locking states.
357 * - static constexpr bool is_timed
358 * True if the lock supports acquiring the lock with a timeout.
359 * - static constexpr bool is_upgrade
360 * True if the lock supports an upgradable state
362 * The following static methods always exist:
363 * - lock(Mutex& mutex)
364 * - unlock(Mutex& mutex)
366 * The following static methods may exist, depending on is_shared, is_timed
371 * - try_lock_shared_for()
374 * - unlock_upgrade_and_lock()
375 * - unlock_and_lock_upgrade()
376 * - unlock_and_lock_shared()
377 * - unlock_upgrade_and_lock_shared()
379 * - try_lock_upgrade_for()
380 * - try_unlock_upgrade_and_lock_for()
387 * Decoupling LockTraits and LockTraitsBase so that if people want to fully
388 * specialize LockTraits then they can inherit from LockTraitsBase instead
389 * of LockTraits with all the same goodies :)
391 template <class Mutex>
392 struct LockTraitsBase
393 : public detail::LockTraitsImpl<
395 detail::MutexLevelValueImpl<
396 detail::LockInterfaceDispatcher<Mutex>::has_lock_unique,
397 detail::LockInterfaceDispatcher<Mutex>::has_lock_shared,
398 detail::LockInterfaceDispatcher<Mutex>::has_lock_upgrade>::value,
399 detail::LockInterfaceDispatcher<Mutex>::has_lock_timed> {};
401 template <class Mutex>
402 struct LockTraits : public LockTraitsBase<Mutex> {};
405 * If the lock is a shared lock, acquire it in shared mode.
406 * Otherwise, for plain (exclusive-only) locks, perform a normal acquire.
408 template <class Mutex>
409 typename std::enable_if<LockTraits<Mutex>::is_shared>::type
410 lock_shared_or_unique(Mutex& mutex) {
411 LockTraits<Mutex>::lock_shared(mutex);
413 template <class Mutex>
414 typename std::enable_if<!LockTraits<Mutex>::is_shared>::type
415 lock_shared_or_unique(Mutex& mutex) {
416 LockTraits<Mutex>::lock(mutex);
420 * If the lock is a shared lock, try to acquire it in shared mode, for up to
421 * the given timeout. Otherwise, for plain (exclusive-only) locks, try to
422 * perform a normal acquire.
424 * Returns true if the lock was acquired, or false on time out.
426 template <class Mutex, class Rep, class Period>
427 typename std::enable_if<LockTraits<Mutex>::is_shared, bool>::type
428 try_lock_shared_or_unique_for(
430 const std::chrono::duration<Rep, Period>& timeout) {
431 return LockTraits<Mutex>::try_lock_shared_for(mutex, timeout);
433 template <class Mutex, class Rep, class Period>
434 typename std::enable_if<!LockTraits<Mutex>::is_shared, bool>::type
435 try_lock_shared_or_unique_for(
437 const std::chrono::duration<Rep, Period>& timeout) {
438 return LockTraits<Mutex>::try_lock_for(mutex, timeout);
442 * Release a lock acquired with lock_shared_or_unique()
444 template <class Mutex>
445 typename std::enable_if<LockTraits<Mutex>::is_shared>::type
446 unlock_shared_or_unique(Mutex& mutex) {
447 LockTraits<Mutex>::unlock_shared(mutex);
449 template <class Mutex>
450 typename std::enable_if<!LockTraits<Mutex>::is_shared>::type
451 unlock_shared_or_unique(Mutex& mutex) {
452 LockTraits<Mutex>::unlock(mutex);
456 * Lock policy classes.
458 * These can be used as template parameters to provide compile-time
459 * selection over the type of lock operation to perform.
463 * A lock policy that performs exclusive lock operations.
465 struct LockPolicyExclusive {
466 template <class Mutex>
467 static void lock(Mutex& mutex) {
468 LockTraits<Mutex>::lock(mutex);
470 template <class Mutex, class Rep, class Period>
471 static bool try_lock_for(
473 const std::chrono::duration<Rep, Period>& timeout) {
474 return LockTraits<Mutex>::try_lock_for(mutex, timeout);
476 template <class Mutex>
477 static void unlock(Mutex& mutex) {
478 LockTraits<Mutex>::unlock(mutex);
483 * A lock policy that performs shared lock operations.
484 * This policy only works with shared mutex types.
486 struct LockPolicyShared {
487 template <class Mutex>
488 static void lock(Mutex& mutex) {
489 LockTraits<Mutex>::lock_shared(mutex);
491 template <class Mutex, class Rep, class Period>
492 static bool try_lock_for(
494 const std::chrono::duration<Rep, Period>& timeout) {
495 return LockTraits<Mutex>::try_lock_shared_for(mutex, timeout);
497 template <class Mutex>
498 static void unlock(Mutex& mutex) {
499 LockTraits<Mutex>::unlock_shared(mutex);
504 * A lock policy that performs a shared lock operation if a shared mutex type
505 * is given, or a normal exclusive lock operation on non-shared mutex types.
507 struct LockPolicyShareable {
508 template <class Mutex>
509 static void lock(Mutex& mutex) {
510 lock_shared_or_unique(mutex);
512 template <class Mutex, class Rep, class Period>
513 static bool try_lock_for(
515 const std::chrono::duration<Rep, Period>& timeout) {
516 return try_lock_shared_or_unique_for(mutex, timeout);
518 template <class Mutex>
519 static void unlock(Mutex& mutex) {
520 unlock_shared_or_unique(mutex);
525 * A lock policy with the following mapping
527 * lock() -> lock_upgrade()
528 * unlock() -> unlock_upgrade()
529 * try_lock_for -> try_lock_upgrade_for()
531 struct LockPolicyUpgrade {
532 template <class Mutex>
533 static void lock(Mutex& mutex) {
534 LockTraits<Mutex>::lock_upgrade(mutex);
536 template <class Mutex, class Rep, class Period>
537 static bool try_lock_for(
539 const std::chrono::duration<Rep, Period>& timeout) {
540 return LockTraits<Mutex>::try_lock_upgrade_for(mutex, timeout);
542 template <class Mutex>
543 static void unlock(Mutex& mutex) {
544 LockTraits<Mutex>::unlock_upgrade(mutex);
548 /*****************************************************************************
549 * Policies for all the transitions from possible mutex levels
550 ****************************************************************************/
552 * A lock policy with the following mapping
554 * lock() -> unlock_upgrade_and_lock()
555 * unlock() -> unlock()
556 * try_lock_for -> try_unlock_upgrade_and_lock_for()
558 struct LockPolicyFromUpgradeToExclusive : public LockPolicyExclusive {
559 template <class Mutex>
560 static void lock(Mutex& mutex) {
561 LockTraits<Mutex>::unlock_upgrade_and_lock(mutex);
563 template <class Mutex, class Rep, class Period>
564 static bool try_lock_for(
566 const std::chrono::duration<Rep, Period>& timeout) {
567 return LockTraits<Mutex>::try_unlock_upgrade_and_lock_for(mutex, timeout);
572 * A lock policy with the following mapping
574 * lock() -> unlock_and_lock_upgrade()
575 * unlock() -> unlock_upgrade()
576 * try_lock_for -> unlock_and_lock_upgrade()
578 struct LockPolicyFromExclusiveToUpgrade : public LockPolicyUpgrade {
579 template <class Mutex>
580 static void lock(Mutex& mutex) {
581 LockTraits<Mutex>::unlock_and_lock_upgrade(mutex);
583 template <class Mutex, class Rep, class Period>
584 static bool try_lock_for(
586 const std::chrono::duration<Rep, Period>&) {
587 LockTraits<Mutex>::unlock_and_lock_upgrade(mutex);
589 // downgrade should be non blocking and should succeed
595 * A lock policy with the following mapping
597 * lock() -> unlock_upgrade_and_lock_shared()
598 * unlock() -> unlock_shared()
599 * try_lock_for -> unlock_upgrade_and_lock_shared()
601 struct LockPolicyFromUpgradeToShared : public LockPolicyShared {
602 template <class Mutex>
603 static void lock(Mutex& mutex) {
604 LockTraits<Mutex>::unlock_upgrade_and_lock_shared(mutex);
606 template <class Mutex, class Rep, class Period>
607 static bool try_lock_for(
609 const std::chrono::duration<Rep, Period>&) {
610 LockTraits<Mutex>::unlock_upgrade_and_lock_shared(mutex);
612 // downgrade should be non blocking and should succeed
618 * A lock policy with the following mapping
620 * lock() -> unlock_and_lock_shared()
621 * unlock() -> unlock_shared()
622 * try_lock_for() -> unlock_and_lock_shared()
624 struct LockPolicyFromExclusiveToShared : public LockPolicyShared {
625 template <class Mutex>
626 static void lock(Mutex& mutex) {
627 LockTraits<Mutex>::unlock_and_lock_shared(mutex);
629 template <class Mutex, class Rep, class Period>
630 static bool try_lock_for(
632 const std::chrono::duration<Rep, Period>&) {
633 LockTraits<Mutex>::unlock_and_lock_shared(mutex);
635 // downgrade should be non blocking and should succeed