| Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| [email protected] | 4410618 | 2012-04-06 03:53:02 | [diff] [blame] | 5 | #include "base/synchronization/lock.h" |
| 6 | |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 7 | #include <stdint.h> |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 8 | |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 9 | #include <algorithm> |
| 10 | #include <atomic> |
| 11 | #include <cmath> |
| 12 | #include <cstdint> |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 13 | #include <memory> |
| 14 | #include <utility> |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 15 | |
| Anand Ravi | 06e44f9 | 2025-05-28 20:15:03 | [diff] [blame] | 16 | #include "base/android/background_thread_pool_field_trial.h" |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 17 | #include "base/check.h" |
| [email protected] | 4410618 | 2012-04-06 03:53:02 | [diff] [blame] | 18 | #include "base/compiler_specific.h" |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 19 | #include "base/dcheck_is_on.h" |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 20 | #include "base/features.h" |
| 21 | #include "base/functional/bind.h" |
| 22 | #include "base/functional/callback.h" |
| 23 | #include "base/functional/callback_helpers.h" |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 24 | #include "base/functional/function_ref.h" |
| 25 | #include "base/location.h" |
| Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 26 | #include "base/memory/raw_ptr.h" |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 27 | #include "base/memory/raw_ref.h" |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 28 | #include "base/profiler/thread_delegate.h" |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 29 | #include "base/rand_util.h" |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 30 | #include "base/synchronization/lock_impl.h" |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 31 | #include "base/synchronization/lock_subtle.h" |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 32 | #include "base/system/sys_info.h" |
| 33 | #include "base/test/bind.h" |
| Gabriel Charette | d773f12 | 2019-03-19 22:06:38 | [diff] [blame] | 34 | #include "base/test/gtest_util.h" |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 35 | #include "base/test/scoped_feature_list.h" |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 36 | #include "base/thread_annotations.h" |
| [email protected] | ce072a7 | 2010-12-31 20:02:16 | [diff] [blame] | 37 | #include "base/threading/platform_thread.h" |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 38 | #include "base/time/time.h" |
| 39 | #include "base/timer/elapsed_timer.h" |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 40 | #include "testing/gmock/include/gmock/gmock.h" |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 41 | #include "testing/gtest/include/gtest/gtest.h" |
| 42 | |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 43 | using testing::UnorderedElementsAre; |
| 44 | using testing::UnorderedElementsAreArray; |
| 45 | |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 46 | namespace base { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 47 | |
| 48 | // Basic test to make sure that Acquire()/Release()/Try() don't crash ---------- |
| 49 | |
| 50 | class BasicLockTestThread : public PlatformThread::Delegate { |
| 51 | public: |
| Peter Kasting | 811504a7 | 2025-01-09 03:18:50 | [diff] [blame] | 52 | explicit BasicLockTestThread(Lock* lock) : lock_(lock) {} |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 53 | |
| Peter Boström | 75cd3c0 | 2021-09-28 15:23:18 | [diff] [blame] | 54 | BasicLockTestThread(const BasicLockTestThread&) = delete; |
| 55 | BasicLockTestThread& operator=(const BasicLockTestThread&) = delete; |
| 56 | |
| dcheng | 5648818 | 2014-10-21 10:54:51 | [diff] [blame] | 57 | void ThreadMain() override { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 58 | for (int i = 0; i < 10; i++) { |
| 59 | lock_->Acquire(); |
| 60 | acquired_++; |
| 61 | lock_->Release(); |
| 62 | } |
| 63 | for (int i = 0; i < 10; i++) { |
| 64 | lock_->Acquire(); |
| 65 | acquired_++; |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 66 | PlatformThread::Sleep(RandTimeDeltaUpTo(Milliseconds(20))); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 67 | lock_->Release(); |
| 68 | } |
| 69 | for (int i = 0; i < 10; i++) { |
| 70 | if (lock_->Try()) { |
| 71 | acquired_++; |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 72 | PlatformThread::Sleep(RandTimeDeltaUpTo(Milliseconds(20))); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 73 | lock_->Release(); |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | int acquired() const { return acquired_; } |
| 79 | |
| 80 | private: |
| Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 81 | raw_ptr<Lock> lock_; |
| Peter Kasting | 811504a7 | 2025-01-09 03:18:50 | [diff] [blame] | 82 | int acquired_ = 0; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 83 | }; |
| 84 | |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 85 | TEST(LockTest, Basic) { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 86 | Lock lock; |
| 87 | BasicLockTestThread thread(&lock); |
| [email protected] | 54e6ff8 | 2013-05-22 00:01:38 | [diff] [blame] | 88 | PlatformThreadHandle handle; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 89 | |
| 90 | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); |
| 91 | |
| 92 | int acquired = 0; |
| 93 | for (int i = 0; i < 5; i++) { |
| 94 | lock.Acquire(); |
| 95 | acquired++; |
| 96 | lock.Release(); |
| 97 | } |
| 98 | for (int i = 0; i < 10; i++) { |
| 99 | lock.Acquire(); |
| 100 | acquired++; |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 101 | PlatformThread::Sleep(RandTimeDeltaUpTo(Milliseconds(20))); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 102 | lock.Release(); |
| 103 | } |
| 104 | for (int i = 0; i < 10; i++) { |
| 105 | if (lock.Try()) { |
| 106 | acquired++; |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 107 | PlatformThread::Sleep(RandTimeDeltaUpTo(Milliseconds(20))); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 108 | lock.Release(); |
| 109 | } |
| 110 | } |
| 111 | for (int i = 0; i < 5; i++) { |
| 112 | lock.Acquire(); |
| 113 | acquired++; |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 114 | PlatformThread::Sleep(RandTimeDeltaUpTo(Milliseconds(20))); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 115 | lock.Release(); |
| 116 | } |
| 117 | |
| 118 | PlatformThread::Join(handle); |
| 119 | |
| 120 | EXPECT_GE(acquired, 20); |
| 121 | EXPECT_GE(thread.acquired(), 20); |
| 122 | } |
| 123 | |
| 124 | // Test that Try() works as expected ------------------------------------------- |
| 125 | |
| 126 | class TryLockTestThread : public PlatformThread::Delegate { |
| 127 | public: |
| Peter Kasting | 811504a7 | 2025-01-09 03:18:50 | [diff] [blame] | 128 | explicit TryLockTestThread(Lock* lock) : lock_(lock) {} |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 129 | |
| Peter Boström | 75cd3c0 | 2021-09-28 15:23:18 | [diff] [blame] | 130 | TryLockTestThread(const TryLockTestThread&) = delete; |
| 131 | TryLockTestThread& operator=(const TryLockTestThread&) = delete; |
| 132 | |
| dcheng | 5648818 | 2014-10-21 10:54:51 | [diff] [blame] | 133 | void ThreadMain() override { |
| Benoît Lizé | a38dbf5d | 2020-03-11 08:33:58 | [diff] [blame] | 134 | // The local variable is required for the static analyzer to see that the |
| 135 | // lock is properly released. |
| 136 | bool got_lock = lock_->Try(); |
| 137 | got_lock_ = got_lock; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 138 | if (got_lock) { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 139 | lock_->Release(); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 140 | } |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | bool got_lock() const { return got_lock_; } |
| 144 | |
| 145 | private: |
| Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 146 | raw_ptr<Lock> lock_; |
| Peter Kasting | 811504a7 | 2025-01-09 03:18:50 | [diff] [blame] | 147 | bool got_lock_ = false; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 148 | }; |
| 149 | |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 150 | TEST(LockTest, TryLock) { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 151 | Lock lock; |
| 152 | |
| 153 | ASSERT_TRUE(lock.Try()); |
| Benoît Lizé | a38dbf5d | 2020-03-11 08:33:58 | [diff] [blame] | 154 | lock.AssertAcquired(); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 155 | |
| 156 | // This thread will not be able to get the lock. |
| 157 | { |
| 158 | TryLockTestThread thread(&lock); |
| [email protected] | 54e6ff8 | 2013-05-22 00:01:38 | [diff] [blame] | 159 | PlatformThreadHandle handle; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 160 | |
| 161 | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); |
| 162 | |
| 163 | PlatformThread::Join(handle); |
| 164 | |
| 165 | ASSERT_FALSE(thread.got_lock()); |
| 166 | } |
| 167 | |
| 168 | lock.Release(); |
| 169 | |
| 170 | // This thread will.... |
| 171 | { |
| 172 | TryLockTestThread thread(&lock); |
| [email protected] | 54e6ff8 | 2013-05-22 00:01:38 | [diff] [blame] | 173 | PlatformThreadHandle handle; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 174 | |
| 175 | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); |
| 176 | |
| 177 | PlatformThread::Join(handle); |
| 178 | |
| 179 | ASSERT_TRUE(thread.got_lock()); |
| 180 | // But it released it.... |
| 181 | ASSERT_TRUE(lock.Try()); |
| Benoît Lizé | a38dbf5d | 2020-03-11 08:33:58 | [diff] [blame] | 182 | lock.AssertAcquired(); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 183 | } |
| 184 | |
| 185 | lock.Release(); |
| 186 | } |
| 187 | |
| 188 | // Tests that locks actually exclude ------------------------------------------- |
| 189 | |
| 190 | class MutexLockTestThread : public PlatformThread::Delegate { |
| 191 | public: |
| 192 | MutexLockTestThread(Lock* lock, int* value) : lock_(lock), value_(value) {} |
| 193 | |
| Peter Boström | 75cd3c0 | 2021-09-28 15:23:18 | [diff] [blame] | 194 | MutexLockTestThread(const MutexLockTestThread&) = delete; |
| 195 | MutexLockTestThread& operator=(const MutexLockTestThread&) = delete; |
| 196 | |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 197 | // Static helper which can also be called from the main thread. |
| 198 | static void DoStuff(Lock* lock, int* value) { |
| 199 | for (int i = 0; i < 40; i++) { |
| 200 | lock->Acquire(); |
| 201 | int v = *value; |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 202 | PlatformThread::Sleep(RandTimeDeltaUpTo(Milliseconds(10))); |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 203 | *value = v + 1; |
| 204 | lock->Release(); |
| 205 | } |
| 206 | } |
| 207 | |
| dcheng | 5648818 | 2014-10-21 10:54:51 | [diff] [blame] | 208 | void ThreadMain() override { DoStuff(lock_, value_); } |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 209 | |
| 210 | private: |
| Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 211 | raw_ptr<Lock> lock_; |
| 212 | raw_ptr<int> value_; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 213 | }; |
| 214 | |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 215 | TEST(LockTest, MutexTwoThreads) { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 216 | Lock lock; |
| 217 | int value = 0; |
| 218 | |
| 219 | MutexLockTestThread thread(&lock, &value); |
| [email protected] | 54e6ff8 | 2013-05-22 00:01:38 | [diff] [blame] | 220 | PlatformThreadHandle handle; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 221 | |
| 222 | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); |
| 223 | |
| 224 | MutexLockTestThread::DoStuff(&lock, &value); |
| 225 | |
| 226 | PlatformThread::Join(handle); |
| 227 | |
| 228 | EXPECT_EQ(2 * 40, value); |
| 229 | } |
| 230 | |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 231 | TEST(LockTest, MutexFourThreads) { |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 232 | Lock lock; |
| 233 | int value = 0; |
| 234 | |
| 235 | MutexLockTestThread thread1(&lock, &value); |
| 236 | MutexLockTestThread thread2(&lock, &value); |
| 237 | MutexLockTestThread thread3(&lock, &value); |
| [email protected] | 54e6ff8 | 2013-05-22 00:01:38 | [diff] [blame] | 238 | PlatformThreadHandle handle1; |
| 239 | PlatformThreadHandle handle2; |
| 240 | PlatformThreadHandle handle3; |
| [email protected] | 5dd879a | 2010-07-30 05:50:42 | [diff] [blame] | 241 | |
| 242 | ASSERT_TRUE(PlatformThread::Create(0, &thread1, &handle1)); |
| 243 | ASSERT_TRUE(PlatformThread::Create(0, &thread2, &handle2)); |
| 244 | ASSERT_TRUE(PlatformThread::Create(0, &thread3, &handle3)); |
| 245 | |
| 246 | MutexLockTestThread::DoStuff(&lock, &value); |
| 247 | |
| 248 | PlatformThread::Join(handle1); |
| 249 | PlatformThread::Join(handle2); |
| 250 | PlatformThread::Join(handle3); |
| 251 | |
| 252 | EXPECT_EQ(4 * 40, value); |
| 253 | } |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 254 | |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 255 | // Test invariant checking ----------------------------------------------------- |
| 256 | |
| 257 | TEST(LockTest, InvariantIsCalled) { |
| 258 | // This test should compile and execute safely regardless of invariant |
| 259 | // checking, but if `kInvariantsActive` is false, we don't expect the |
| 260 | // invariant to be checked when the lock state changes. |
| 261 | constexpr bool kInvariantsActive = DCHECK_IS_ON(); |
| 262 | |
| 263 | class InvariantChecker { |
| 264 | public: |
| 265 | explicit InvariantChecker(const Lock& lock LIFETIME_BOUND) : lock(lock) {} |
| 266 | void Check() ASSERT_EXCLUSIVE_LOCK(lock) { |
| 267 | lock->AssertAcquired(); |
| 268 | invariant_called = true; |
| 269 | } |
| 270 | bool TestAndReset() { return std::exchange(invariant_called, false); } |
| 271 | |
| 272 | private: |
| 273 | const raw_ref<const Lock> lock; |
| 274 | bool invariant_called = false; |
| 275 | }; |
| 276 | |
| 277 | // Awkward construction order here allows `checker` to refer to `lock`, which |
| 278 | // refers to `check_ref`, which refers to `check`, which refers to `checker`. |
| 279 | std::unique_ptr<InvariantChecker> checker; |
| 280 | auto check = [&] { checker->Check(); }; |
| 281 | auto check_ref = base::FunctionRef<void()>(check); |
| Hans Wennborg | 2b66b03 | 2025-03-11 13:00:04 | [diff] [blame] | 282 | Lock lock([&](Lock* lp) { |
| 283 | checker = std::make_unique<InvariantChecker>(*lp); |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 284 | return check_ref; |
| Hans Wennborg | 2b66b03 | 2025-03-11 13:00:04 | [diff] [blame] | 285 | }(&lock)); |
| Peter Kasting | e7fb794 | 2024-12-06 22:43:05 | [diff] [blame] | 286 | |
| 287 | EXPECT_FALSE(checker->TestAndReset()); |
| 288 | |
| 289 | lock.Acquire(); |
| 290 | EXPECT_EQ(kInvariantsActive, checker->TestAndReset()); |
| 291 | |
| 292 | lock.Release(); |
| 293 | EXPECT_EQ(kInvariantsActive, checker->TestAndReset()); |
| 294 | } |
| 295 | |
| 296 | // AutoLock tests -------------------------------------------------------------- |
| 297 | |
| Gabriel Charette | d773f12 | 2019-03-19 22:06:38 | [diff] [blame] | 298 | TEST(LockTest, AutoLockMaybe) { |
| 299 | Lock lock; |
| 300 | { |
| 301 | AutoLockMaybe auto_lock(&lock); |
| 302 | lock.AssertAcquired(); |
| 303 | } |
| 304 | EXPECT_DCHECK_DEATH(lock.AssertAcquired()); |
| 305 | } |
| 306 | |
| 307 | TEST(LockTest, AutoLockMaybeNull) { |
| 308 | AutoLockMaybe auto_lock(nullptr); |
| 309 | } |
| 310 | |
| 311 | TEST(LockTest, ReleasableAutoLockExplicitRelease) { |
| 312 | Lock lock; |
| 313 | ReleasableAutoLock auto_lock(&lock); |
| 314 | lock.AssertAcquired(); |
| 315 | auto_lock.Release(); |
| 316 | EXPECT_DCHECK_DEATH(lock.AssertAcquired()); |
| 317 | } |
| 318 | |
| 319 | TEST(LockTest, ReleasableAutoLockImplicitRelease) { |
| 320 | Lock lock; |
| 321 | { |
| 322 | ReleasableAutoLock auto_lock(&lock); |
| 323 | lock.AssertAcquired(); |
| 324 | } |
| 325 | EXPECT_DCHECK_DEATH(lock.AssertAcquired()); |
| 326 | } |
| 327 | |
| Dan McArdle | f97ab60 | 2024-05-16 15:44:59 | [diff] [blame] | 328 | class TryLockTest : public testing::Test { |
| 329 | protected: |
| 330 | Lock lock_; |
| 331 | int x_ GUARDED_BY(lock_) = 0; |
| 332 | }; |
| 333 | |
| 334 | // Verifies thread safety annotations do not prevent correct `AutoTryLock` usage |
| 335 | // from compiling. A dual of this test exists in lock_nocompile.nc. For more |
| 336 | // context, see <https://crbug.com/340196356>. |
| 337 | TEST_F(TryLockTest, CorrectlyCheckIsAcquired) { |
| 338 | AutoTryLock maybe(lock_); |
| 339 | // Should compile because we correctly check whether the lock is acquired |
| 340 | // before writing to `x_`. |
| 341 | if (maybe.is_acquired()) { |
| 342 | x_ = 5; |
| 343 | } |
| 344 | } |
| 345 | |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 346 | #if DCHECK_IS_ON() |
| 347 | |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 348 | TEST(LockTest, GetTrackedLocksHeldByCurrentThread) { |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 349 | Lock lock_a; |
| 350 | Lock lock_b; |
| 351 | Lock lock_c; |
| 352 | const uintptr_t lock_a_ptr = reinterpret_cast<uintptr_t>(&lock_a); |
| 353 | const uintptr_t lock_b_ptr = reinterpret_cast<uintptr_t>(&lock_b); |
| 354 | const uintptr_t lock_c_ptr = reinterpret_cast<uintptr_t>(&lock_c); |
| 355 | |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 356 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| 357 | UnorderedElementsAre()); |
| 358 | ReleasableAutoLock auto_lock_a(&lock_a, subtle::LockTracking::kEnabled); |
| 359 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 360 | UnorderedElementsAre(lock_a_ptr)); |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 361 | ReleasableAutoLock auto_lock_b(&lock_b, subtle::LockTracking::kEnabled); |
| 362 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 363 | UnorderedElementsAre(lock_a_ptr, lock_b_ptr)); |
| 364 | auto_lock_a.Release(); |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 365 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 366 | UnorderedElementsAre(lock_b_ptr)); |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 367 | ReleasableAutoLock auto_lock_c(&lock_c, subtle::LockTracking::kEnabled); |
| 368 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 369 | UnorderedElementsAre(lock_b_ptr, lock_c_ptr)); |
| 370 | auto_lock_c.Release(); |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 371 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 372 | UnorderedElementsAre(lock_b_ptr)); |
| 373 | auto_lock_b.Release(); |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 374 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| 375 | UnorderedElementsAre()); |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 376 | } |
| 377 | |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 378 | TEST(LockTest, GetTrackedLocksHeldByCurrentThread_AutoLock) { |
| 379 | Lock lock; |
| 380 | const uintptr_t lock_ptr = reinterpret_cast<uintptr_t>(&lock); |
| 381 | AutoLock auto_lock(lock, subtle::LockTracking::kEnabled); |
| 382 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| 383 | UnorderedElementsAre(lock_ptr)); |
| 384 | } |
| 385 | |
| 386 | TEST(LockTest, GetTrackedLocksHeldByCurrentThread_MovableAutoLock) { |
| 387 | Lock lock; |
| 388 | const uintptr_t lock_ptr = reinterpret_cast<uintptr_t>(&lock); |
| 389 | MovableAutoLock auto_lock(lock, subtle::LockTracking::kEnabled); |
| 390 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| 391 | UnorderedElementsAre(lock_ptr)); |
| 392 | } |
| 393 | |
| 394 | TEST(LockTest, GetTrackedLocksHeldByCurrentThread_AutoTryLock) { |
| 395 | Lock lock; |
| 396 | const uintptr_t lock_ptr = reinterpret_cast<uintptr_t>(&lock); |
| 397 | AutoTryLock auto_lock(lock, subtle::LockTracking::kEnabled); |
| 398 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| 399 | UnorderedElementsAre(lock_ptr)); |
| 400 | } |
| 401 | |
| 402 | TEST(LockTest, GetTrackedLocksHeldByCurrentThread_AutoLockMaybe) { |
| 403 | Lock lock; |
| 404 | const uintptr_t lock_ptr = reinterpret_cast<uintptr_t>(&lock); |
| 405 | AutoLockMaybe auto_lock(&lock, subtle::LockTracking::kEnabled); |
| 406 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| 407 | UnorderedElementsAre(lock_ptr)); |
| 408 | } |
| 409 | |
| 410 | TEST(LockTest, GetTrackedLocksHeldByCurrentThreadOverCapacity) |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 411 | // Thread-safety analysis doesn't handle the array of locks properly. |
| 412 | NO_THREAD_SAFETY_ANALYSIS { |
| 413 | constexpr size_t kHeldLocksCapacity = 10; |
| 414 | std::array<Lock, kHeldLocksCapacity + 1> locks; |
| 415 | |
| 416 | for (size_t i = 0; i < kHeldLocksCapacity; ++i) { |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 417 | locks[i].Acquire(subtle::LockTracking::kEnabled); |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 418 | } |
| 419 | |
| Peter Boström | 59a246e8 | 2025-03-17 03:55:33 | [diff] [blame] | 420 | EXPECT_CHECK_DEATH({ |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 421 | locks[kHeldLocksCapacity].Acquire(subtle::LockTracking::kEnabled); |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 422 | locks[kHeldLocksCapacity].Release(); |
| 423 | }); |
| 424 | |
| 425 | for (size_t i = 0; i < kHeldLocksCapacity; ++i) { |
| 426 | locks[i].Release(); |
| 427 | |
| 428 | std::vector<uintptr_t> expected_locks; |
| 429 | for (size_t j = i + 1; j < kHeldLocksCapacity; ++j) { |
| 430 | expected_locks.push_back(reinterpret_cast<uintptr_t>(&locks[j])); |
| 431 | } |
| 432 | |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 433 | EXPECT_THAT(subtle::GetTrackedLocksHeldByCurrentThread(), |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 434 | UnorderedElementsAreArray(expected_locks)); |
| 435 | } |
| 436 | } |
| 437 | |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 438 | TEST(LockTest, TrackingDisabled) { |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 439 | Lock lock; |
| François Doray | 6671c40 | 2024-06-21 17:47:39 | [diff] [blame] | 440 | AutoLock auto_lock(lock, subtle::LockTracking::kDisabled); |
| 441 | EXPECT_TRUE(subtle::GetTrackedLocksHeldByCurrentThread().empty()); |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 442 | } |
| 443 | |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 444 | // Priority Inheritance Tests -------------------------------------------------- |
| 445 | |
| 446 | #if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE) |
| 447 | namespace { |
| 448 | class PriorityInheritanceTest { |
| 449 | public: |
| 450 | // The average value of MeasureRunTime() over |num_samples| iterations. |
| 451 | static TimeDelta MeasureAverageRunTime(int num_samples = 10) { |
| 452 | TimeDelta total_runtime; |
| 453 | for (int i = 0; i < num_samples; i++) { |
| 454 | total_runtime += MeasureRunTime(); |
| 455 | } |
| 456 | |
| 457 | return total_runtime / num_samples; |
| 458 | } |
| 459 | |
| 460 | // Measure the time taken for a low-priority thread (kBackground) to perform |
| 461 | // CPU bound work when it holds a lock that is awaited by a high-priority |
| 462 | // thread (kRealtimeAudio). |
| 463 | static TimeDelta MeasureRunTime() { |
| 464 | Lock lock; |
| 465 | TimeDelta test_run_time; |
| 466 | std::atomic<bool> signal_cpu_bound_worker_threads_shutdown{false}, |
| 467 | signal_thread_a_will_lock{false}; |
| 468 | |
| 469 | // Keep all the cores busy with a workload of CPU bound thread to reduce |
| 470 | // flakiness in the test by skewing the CPU time between the high-priority |
| 471 | // and low-priority measurement threads. |
| 472 | std::vector<TestThread> cpu_bound_worker_threads; |
| 473 | for (int i = 0; i < 15; i++) { |
| 474 | cpu_bound_worker_threads.emplace_back( |
| 475 | ThreadType::kDefault, base::BindLambdaForTesting([&]() { |
| 476 | while (!signal_cpu_bound_worker_threads_shutdown.load( |
| 477 | std::memory_order_relaxed)) { |
| 478 | BusyLoop(10); |
| 479 | } |
| 480 | })); |
| 481 | } |
| 482 | |
| 483 | for (auto& worker_thread : cpu_bound_worker_threads) { |
| 484 | worker_thread.Create(); |
| 485 | } |
| 486 | |
| 487 | TestThread thread_a( |
| 488 | ThreadType::kRealtimeAudio, base::BindLambdaForTesting([&]() { |
| 489 | // Signal to thread B that the current thread will acquire the lock |
| 490 | // next, so that it can to start its CPU bound work. |
| 491 | signal_thread_a_will_lock.store(true, std::memory_order_relaxed); |
| 492 | |
| 493 | // Wait on the lock to be released once the low-priority thread is |
| 494 | // done. In the case when priority inheritance mutexes are enabled, |
| 495 | // this should boost the priority of the low-priority thread to the |
| 496 | // priority of the highest priority waiter (i.e. the current thread). |
| 497 | AutoLock auto_lock(lock); |
| 498 | BusyLoop(10); |
| 499 | })); |
| 500 | |
| 501 | TestThread thread_b( |
| 502 | ThreadType::kBackground, base::BindLambdaForTesting([&]() { |
| 503 | // Acquire the lock before creating the high-priority thread, so that |
| 504 | // the higher priority thread is blocked on the current thread while |
| 505 | // the current thread performs CPU-bound work. |
| 506 | AutoLock auto_lock(lock); |
| 507 | thread_a.Create(); |
| 508 | |
| 509 | // Before performing the CPU bound work, wait for the thread A to |
| 510 | // signal that it has started running and will acquire the lock next. |
| 511 | // While it is not a perfectly reliable signal (thread A may get |
| 512 | // descheduled immediately after signalling), given the relative |
| 513 | // priorities of the two threads it is good enough to reduce large |
| 514 | // variations due to latencies in thread bring up. |
| 515 | while (!signal_thread_a_will_lock.load(std::memory_order_relaxed)) { |
| 516 | usleep(10); |
| 517 | } |
| 518 | |
| 519 | ElapsedTimer timer; |
| 520 | BusyLoop(1000000); |
| 521 | test_run_time = timer.Elapsed(); |
| 522 | })); |
| 523 | |
| 524 | // Create the low-priority thread which is responsible for creating the |
| 525 | // high-priority thread. Wait for both threads to finish before recording |
| 526 | // the elapsed time. |
| 527 | thread_b.Create(); |
| 528 | thread_b.Join(); |
| 529 | thread_a.Join(); |
| 530 | |
| 531 | signal_cpu_bound_worker_threads_shutdown.store(true, |
| 532 | std::memory_order_relaxed); |
| 533 | for (auto& worker_thread : cpu_bound_worker_threads) { |
| 534 | worker_thread.Join(); |
| 535 | } |
| 536 | |
| 537 | return test_run_time; |
| 538 | } |
| 539 | |
| 540 | private: |
| 541 | // CPU bound work for the threads to eat up CPU cycles. |
| 542 | static void BusyLoop(size_t n) { |
| 543 | __unused int sum = 0; |
| 544 | for (int i = 0; i < n; i++) { |
| 545 | if (base::ShouldRecordSubsampledMetric(0.5)) { |
| 546 | sum += 1; |
| 547 | } |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | class TestThread : public PlatformThread::Delegate { |
| 552 | public: |
| 553 | explicit TestThread(ThreadType thread_type, base::OnceClosure body) |
| 554 | : thread_type_(thread_type), body_(std::move(body)) {} |
| 555 | |
| 556 | void Create() { |
| 557 | ASSERT_TRUE( |
| 558 | PlatformThread::CreateWithType(0, this, &handle_, thread_type_)); |
| 559 | } |
| 560 | |
| 561 | void ThreadMain() override { std::move(body_).Run(); } |
| 562 | |
| 563 | void Join() { PlatformThread::Join(handle_); } |
| 564 | |
| 565 | private: |
| 566 | ThreadType thread_type_; |
| 567 | PlatformThreadHandle handle_; |
| 568 | base::OnceClosure body_; |
| 569 | }; |
| 570 | }; |
| 571 | |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 572 | } // namespace |
| 573 | |
| 574 | // Tests that the time taken by a higher-priority thread to acquire a lock held |
| 575 | // by a lower-priority thread is indeed reduced by priority inheritance. |
| 576 | TEST(LockTest, PriorityIsInherited) { |
| 577 | TimeDelta avg_test_run_time_with_pi, avg_test_run_time_without_pi; |
| 578 | |
| Anand Ravi | 06e44f9 | 2025-05-28 20:15:03 | [diff] [blame] | 579 | // Priority inheritance mutexes are not supported on Android kernels < 6.1 |
| 580 | if (!base::KernelSupportsPriorityInheritanceFutex()) { |
| 581 | GTEST_SKIP() << "base::Lock does not handle multiple thread priorities " |
| 582 | << "(Kernel version: " |
| 583 | << base::SysInfo::KernelVersionNumber::Current() << ")"; |
| 584 | } |
| 585 | |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 586 | { |
| Anand Ravi | 06e44f9 | 2025-05-28 20:15:03 | [diff] [blame] | 587 | base::android::ScopedUsePriorityInheritanceLocksForTesting use_pi_locks; |
| 588 | ASSERT_TRUE(base::android::BackgroundThreadPoolFieldTrial:: |
| 589 | ShouldUsePriorityInheritanceLocks()); |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 590 | avg_test_run_time_with_pi = |
| 591 | PriorityInheritanceTest::MeasureAverageRunTime(); |
| 592 | } |
| 593 | |
| 594 | { |
| Anand Ravi | 06e44f9 | 2025-05-28 20:15:03 | [diff] [blame] | 595 | ASSERT_TRUE(!base::android::BackgroundThreadPoolFieldTrial:: |
| 596 | ShouldUsePriorityInheritanceLocks()); |
| Anand Ravi | 9d185cd68 | 2025-03-27 16:41:52 | [diff] [blame] | 597 | avg_test_run_time_without_pi = |
| 598 | PriorityInheritanceTest::MeasureAverageRunTime(); |
| 599 | } |
| 600 | |
| 601 | // During the time in which the thread A is waiting on the lock to be released |
| 602 | // by the thread B, the thread B runs at kBackground priority in the non-PI |
| 603 | // case and at kRealtimeAudio priority in the PI case. |
| 604 | // |
| 605 | // Based on the Linux kernel's allocation of CPU shares documented in |
| 606 | // https://elixir.bootlin.com/linux/v6.12.5/source/kernel/sched/core.c#L9998, |
| 607 | // a thread running at kRealtimeAudio (nice value = -16) gets 36291 shares |
| 608 | // of the CPU, a thread at kDefault (nice value = 0) get 1024 shares and a |
| 609 | // thread at kBackground (nice value = 10) gets 110 shares of the CPU. |
| 610 | // |
| 611 | // Assuming no other threads except the ones created by this test are running, |
| 612 | // during the time in which thread A is waiting on the lock to be released by |
| 613 | // thread B, thread B gets 110/(15*1024 + 110) ≈ 0.7% of the CPU time in the |
| 614 | // non-PI case and 36291/(36291 + 15*1024) ≈ 70% of the CPU time in the PI |
| 615 | // case. This is approximately a 100x difference in CPU shares allocated to |
| 616 | // the thread B when it is doing CPU-bound work. |
| 617 | // |
| 618 | // The test is thus designed such that the measured run time is thread B's CPU |
| 619 | // bound work. While there are other factors at play that determine the |
| 620 | // measured run time such as the frequency at which the CPU is running, we can |
| 621 | // expect that there will be at least an order of magnitude of disparity in |
| 622 | // the test run times with and without PI. |
| 623 | // |
| 624 | // In order to reduce test flakiness while still eliminating the possibility |
| 625 | // of variance in measurements accounting for the test results, we |
| 626 | // conservatively expect a 3x improvement. |
| 627 | EXPECT_GT(avg_test_run_time_without_pi, 3 * avg_test_run_time_with_pi); |
| 628 | } |
| 629 | #endif // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE) |
| 630 | |
| François Doray | efe18b9 | 2024-05-31 21:44:59 | [diff] [blame] | 631 | #endif // DCHECK_IS_ON() |
| 632 | |
| [email protected] | bc581a68 | 2011-01-01 23:16:20 | [diff] [blame] | 633 | } // namespace base |