ScopedLockTests.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/std/parallel/scoped_lock.h>
  9. #include <AzCore/std/parallel/shared_mutex.h>
  10. #include "UserTypes.h"
  11. namespace UnitTest
  12. {
  13. // Fixture for AZStd::scoped_lock unit tests
  14. class ScopedLockTest
  15. : public LeakDetectionFixture
  16. {
  17. };
  18. struct ScopedTestMutex
  19. {
  20. ScopedTestMutex() = default;
  21. void lock()
  22. {
  23. m_locked = true;
  24. }
  25. void unlock()
  26. {
  27. m_locked = false;
  28. }
  29. bool try_lock()
  30. {
  31. if (m_locked)
  32. {
  33. return false;
  34. }
  35. m_locked = true;
  36. return m_locked;
  37. }
  38. bool m_locked{};
  39. };
  40. struct AtomicScopedTestMutex
  41. {
  42. AtomicScopedTestMutex() = default;
  43. void lock()
  44. {
  45. AZStd::exponential_backoff waitForCounter;
  46. constexpr bool lockedCounterValue{ true };
  47. while (true)
  48. {
  49. bool unlockedCounterValue{ false };
  50. if (m_locked.compare_exchange_weak(unlockedCounterValue, lockedCounterValue, AZStd::memory_order_release))
  51. {
  52. break;
  53. }
  54. waitForCounter.wait();
  55. }
  56. }
  57. void unlock()
  58. {
  59. m_locked.store(false, AZStd::memory_order_release);
  60. }
  61. bool try_lock()
  62. {
  63. bool unlockedCounterValue{ false };
  64. constexpr bool lockedCounterValue{ true };
  65. return m_locked.compare_exchange_strong(unlockedCounterValue, lockedCounterValue, AZStd::memory_order_release);;
  66. }
  67. AZStd::atomic<bool> m_locked{};
  68. };
  69. TEST_F(ScopedLockTest, scoped_lock_NoArgument_Construct_ConstructorSucceeds)
  70. {
  71. // Validates empty mutex scoped_lock is constructible and destructible
  72. AZStd::scoped_lock<> emptyLock;
  73. (void)emptyLock;
  74. }
  75. TEST_F(ScopedLockTest, scoped_lock_OneArgument_Construct_ConstructorSucceeds)
  76. {
  77. ScopedTestMutex testMutex1;
  78. {
  79. AZStd::scoped_lock<decltype(testMutex1)> oneArgLock(testMutex1);
  80. EXPECT_TRUE(testMutex1.m_locked);
  81. }
  82. EXPECT_FALSE(testMutex1.m_locked);
  83. }
  84. TEST_F(ScopedLockTest, scoped_lock_TwoArgument_Construct_ConstructorSucceeds)
  85. {
  86. ScopedTestMutex testMutex1;
  87. ScopedTestMutex testMutex2;
  88. {
  89. AZStd::scoped_lock<decltype(testMutex1), decltype(testMutex2)> twoArgLock(testMutex1, testMutex2);
  90. EXPECT_TRUE(testMutex1.m_locked);
  91. EXPECT_TRUE(testMutex2.m_locked);
  92. }
  93. EXPECT_FALSE(testMutex1.m_locked);
  94. EXPECT_FALSE(testMutex2.m_locked);
  95. }
  96. TEST_F(ScopedLockTest, scoped_lock_VariadicArgument_Construct_ConstructorSucceeds)
  97. {
  98. ScopedTestMutex testMutex1;
  99. ScopedTestMutex testMutex2;
  100. ScopedTestMutex testMutex3;
  101. {
  102. AZStd::scoped_lock<decltype(testMutex1), decltype(testMutex2), decltype(testMutex3)> threeArgLock(testMutex1, testMutex2, testMutex3);
  103. EXPECT_TRUE(testMutex1.m_locked);
  104. EXPECT_TRUE(testMutex2.m_locked);
  105. EXPECT_TRUE(testMutex3.m_locked);
  106. }
  107. EXPECT_FALSE(testMutex1.m_locked);
  108. EXPECT_FALSE(testMutex2.m_locked);
  109. EXPECT_FALSE(testMutex3.m_locked);
  110. }
  111. inline namespace ScopedLockInternal
  112. {
  113. struct NotAType {};
  114. template<typename T, typename = void>
  115. struct has_mutex_type
  116. {
  117. static constexpr bool value{ false };
  118. };
  119. template<typename T>
  120. struct has_mutex_type<T, AZStd::void_t<typename T::mutex_type>>
  121. {
  122. static constexpr bool value{ true };
  123. };
  124. template<typename T>
  125. constexpr bool has_mutex_type_v = has_mutex_type<T>::value;
  126. }
  127. TEST_F(ScopedLockTest, scoped_lock_mutex_type_trait_ExistOnlyForOneArgumentTemplate)
  128. {
  129. static_assert(!ScopedLockInternal::has_mutex_type_v<AZStd::scoped_lock<>>, "ScopedLock with empty parameter should not have the mutex_type alias");
  130. {
  131. using MutexType = ScopedTestMutex;
  132. using ScopedLockType = AZStd::scoped_lock<MutexType>;
  133. static_assert(ScopedLockInternal::has_mutex_type_v<ScopedLockType>, "ScopedLock with one parameter type should have a mutex_type alias");
  134. static_assert(AZStd::is_same<typename ScopedLockType::mutex_type, MutexType>::value, "ScopedLock mutex_type alias should match MutexType alias");
  135. }
  136. {
  137. using MutexType = AZStd::recursive_mutex;
  138. using ScopedLockType = AZStd::scoped_lock<MutexType>;
  139. static_assert(ScopedLockInternal::has_mutex_type_v<ScopedLockType>, "ScopedLock with one parameter type should have a mutex_type alias");
  140. static_assert(AZStd::is_same<typename ScopedLockType::mutex_type, MutexType>::value, "ScopedLock mutex_type alias should match MutexType alias");
  141. }
  142. static_assert(!ScopedLockInternal::has_mutex_type_v<AZStd::scoped_lock<ScopedTestMutex, ScopedTestMutex>>, "ScopedLock with two template parameter types should not have a mutex_type alias");
  143. static_assert(!ScopedLockInternal::has_mutex_type_v<AZStd::scoped_lock<AZStd::recursive_mutex, ScopedTestMutex>>, "ScopedLock with two template parameter types should not have a mutex_type alias");
  144. static_assert(!ScopedLockInternal::has_mutex_type_v<AZStd::scoped_lock<ScopedTestMutex, AZStd::recursive_mutex, ScopedTestMutex>>, "ScopedLock with two template parameter types should not have a mutex_type alias");
  145. }
  146. TEST_F(ScopedLockTest, scoped_lock_NoArgument_adopt_lock_Construct_ConstructorSucceeds)
  147. {
  148. // Validates empty mutex scoped_lock is construticble and destructible
  149. AZStd::scoped_lock<> emptyLock(AZStd::adopt_lock);
  150. }
  151. TEST_F(ScopedLockTest, scoped_lock_OneArgument_adopt_lock_Construct_ConstructorSucceeds)
  152. {
  153. ScopedTestMutex testMutex1;
  154. {
  155. AZStd::scoped_lock<decltype(testMutex1)> oneArgLock(AZStd::adopt_lock, testMutex1);
  156. EXPECT_FALSE(testMutex1.m_locked);
  157. }
  158. testMutex1.lock();
  159. {
  160. AZStd::scoped_lock<decltype(testMutex1)> oneArgLock(AZStd::adopt_lock, testMutex1);
  161. EXPECT_TRUE(testMutex1.m_locked);
  162. }
  163. EXPECT_FALSE(testMutex1.m_locked);
  164. }
  165. TEST_F(ScopedLockTest, scoped_lock_TwoArgument_adopt_lock_Construct_ConstructorSucceeds)
  166. {
  167. ScopedTestMutex testMutex1;
  168. ScopedTestMutex testMutex2;
  169. testMutex2.lock();
  170. {
  171. AZStd::scoped_lock<decltype(testMutex1), decltype(testMutex2)> twoArgLock(AZStd::adopt_lock, testMutex1, testMutex2);
  172. EXPECT_FALSE(testMutex1.m_locked);
  173. EXPECT_TRUE(testMutex2.m_locked);
  174. }
  175. EXPECT_FALSE(testMutex1.m_locked);
  176. EXPECT_FALSE(testMutex2.m_locked);
  177. }
  178. TEST_F(ScopedLockTest, scoped_lock_VariadicArgument_adopt_lock_Construct_ConstructorSucceeds)
  179. {
  180. ScopedTestMutex testMutex1;
  181. ScopedTestMutex testMutex2;
  182. ScopedTestMutex testMutex3;
  183. testMutex1.lock();
  184. testMutex3.lock();
  185. {
  186. AZStd::scoped_lock<decltype(testMutex1), decltype(testMutex2), decltype(testMutex3)> threeArgLock(AZStd::adopt_lock, testMutex1, testMutex2, testMutex3);
  187. EXPECT_TRUE(testMutex1.m_locked);
  188. EXPECT_FALSE(testMutex2.m_locked);
  189. EXPECT_TRUE(testMutex3.m_locked);
  190. }
  191. EXPECT_FALSE(testMutex1.m_locked);
  192. EXPECT_FALSE(testMutex2.m_locked);
  193. EXPECT_FALSE(testMutex3.m_locked);
  194. }
  195. TEST_F(ScopedLockTest, Deadlock_AvoidanceTest_Recursive_And_Shared_Mutex)
  196. {
  197. constexpr size_t threadCount = 8;
  198. enum : size_t { cycleCount = 1000 };
  199. constexpr uint64_t expectedCounterResult = threadCount * cycleCount;
  200. AZStd::thread threads[threadCount];
  201. AZStd::recursive_mutex recursiveMutex1;
  202. AZStd::shared_mutex sharedMutex1;
  203. uint64_t testCounter{};
  204. auto work = [&testCounter, &recursiveMutex1, &sharedMutex1]()
  205. {
  206. for (size_t cycleIndex = 0; cycleIndex != cycleCount; ++cycleIndex)
  207. {
  208. AZStd::scoped_lock<AZStd::recursive_mutex, AZStd::shared_mutex> recursiveAndSharedMutexLock(recursiveMutex1, sharedMutex1);
  209. ++testCounter;
  210. }
  211. };
  212. for (AZStd::thread& thread : threads)
  213. {
  214. thread = AZStd::thread(work);
  215. }
  216. for (AZStd::thread& thread : threads)
  217. {
  218. thread.join();
  219. }
  220. EXPECT_EQ(expectedCounterResult, testCounter);
  221. }
  222. TEST_F(ScopedLockTest, Deadlock_AvoidanceTest_Swap_Lock_Order_For_Half_Threads)
  223. {
  224. constexpr size_t threadCount = 8;
  225. enum : size_t { cycleCount = 1000 };
  226. constexpr uint64_t expectedCounterResult = threadCount * cycleCount;
  227. AZStd::thread threads[threadCount];
  228. AZStd::recursive_mutex recursiveMutex1;
  229. AZStd::shared_mutex sharedMutex1;
  230. uint64_t testCounter{};
  231. auto evenThreadWorkerFunc = [&testCounter, &recursiveMutex1, &sharedMutex1]()
  232. {
  233. for (size_t cycleIndex = 0; cycleIndex != cycleCount; ++cycleIndex)
  234. {
  235. AZStd::scoped_lock<AZStd::recursive_mutex, AZStd::shared_mutex> recursiveAndSharedMutexLock(recursiveMutex1, sharedMutex1);
  236. ++testCounter;
  237. }
  238. };
  239. // Just swaps the parameter order so that the shared_mutex comes before the recursive mutex
  240. auto oddThreadWorkerFunc = [&testCounter, &recursiveMutex1, &sharedMutex1]()
  241. {
  242. for (size_t cycleIndex = 0; cycleIndex != cycleCount; ++cycleIndex)
  243. {
  244. AZStd::scoped_lock<AZStd::shared_mutex, AZStd::recursive_mutex> recursiveAndSharedMutexLock(sharedMutex1, recursiveMutex1);
  245. ++testCounter;
  246. }
  247. };
  248. size_t threadIndex = 0;
  249. for (AZStd::thread& threadRef : threads)
  250. {
  251. if (threadIndex % 2 == 0)
  252. {
  253. threadRef = AZStd::thread(evenThreadWorkerFunc);
  254. }
  255. else
  256. {
  257. threadRef = AZStd::thread(oddThreadWorkerFunc);
  258. }
  259. ++threadIndex;
  260. }
  261. for (AZStd::thread& threadRef : threads)
  262. {
  263. threadRef.join();
  264. }
  265. EXPECT_EQ(expectedCounterResult, testCounter);
  266. }
  267. TEST_F(ScopedLockTest, Deadlock_AvoidanceTest_Atomic_Test_Mutex)
  268. {
  269. constexpr size_t threadCount = 8;
  270. enum : size_t { cycleCount = 1000 };
  271. constexpr uint64_t expectedCounterResult = threadCount * cycleCount;
  272. AZStd::thread threads[threadCount];
  273. AtomicScopedTestMutex testMutex1;
  274. AtomicScopedTestMutex testMutex2;
  275. uint64_t testCounter{};
  276. auto work = [&testCounter, &testMutex1, &testMutex2]()
  277. {
  278. for (size_t cycleIndex = 0; cycleIndex != cycleCount; ++cycleIndex)
  279. {
  280. AZStd::scoped_lock<AtomicScopedTestMutex, AtomicScopedTestMutex> recursiveAndSharedMutexLock(testMutex1, testMutex2);
  281. ++testCounter;
  282. }
  283. };
  284. for (AZStd::thread& thread : threads)
  285. {
  286. thread = AZStd::thread(work);
  287. }
  288. for (AZStd::thread& thread : threads)
  289. {
  290. thread.join();
  291. }
  292. EXPECT_EQ(expectedCounterResult, testCounter);
  293. }
  294. }