• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // This file is used for debugging assertion support.  The Lock class
6 // is functionally a wrapper around the LockImpl class, so the only
7 // real intelligence in the class is in the debugging logic.
8 
9 #ifdef UNSAFE_BUFFERS_BUILD
10 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
11 #pragma allow_unsafe_buffers
12 #endif
13 
14 #include "base/synchronization/lock.h"
15 
16 #include <cstdint>
17 
18 #if DCHECK_IS_ON()
19 #include <array>
20 
21 #include "base/synchronization/lock_subtle.h"
22 #include "base/threading/platform_thread.h"
23 
24 namespace base {
25 
26 namespace {
27 
28 // List of locks held by a thread.
29 //
30 // As of May 2024, no more than 5 locks were held simultaneously by a thread in
31 // a test browsing session or while running the CQ (% locks acquired in unit
32 // tests "WaitSetTest.NoStarvation" and
33 // "MessagePipeTest.DataPipeConsumerHandlePingPong"). An array of size 10 is
34 // therefore considered sufficient to track all locks held by a thread. A
35 // dynamic-size array (e.g. owned by a `ThreadLocalOwnedPointer`) would require
36 // handling reentrancy issues with allocator shims that use `base::Lock`.
37 constexpr size_t kHeldLocksCapacity = 10;
38 thread_local std::array<uintptr_t, kHeldLocksCapacity>
39     g_tracked_locks_held_by_thread;
40 
41 // Number of non-nullptr elements in `g_tracked_locks_held_by_thread`.
42 thread_local size_t g_num_tracked_locks_held_by_thread = 0;
43 
44 }  // namespace
45 
~Lock()46 Lock::~Lock() {
47   DCHECK(owning_thread_ref_.is_null());
48 }
49 
Acquire(subtle::LockTracking tracking)50 void Lock::Acquire(subtle::LockTracking tracking) {
51   lock_.Lock();
52   if (tracking == subtle::LockTracking::kEnabled) {
53     AddToLocksHeldOnCurrentThread();
54   }
55   CheckUnheldAndMark();
56 }
57 
Release()58 void Lock::Release() {
59   CheckHeldAndUnmark();
60   if (in_tracked_locks_held_by_current_thread_) {
61     RemoveFromLocksHeldOnCurrentThread();
62   }
63   lock_.Unlock();
64 }
65 
Try(subtle::LockTracking tracking)66 bool Lock::Try(subtle::LockTracking tracking) {
67   const bool rv = lock_.Try();
68   if (rv) {
69     if (tracking == subtle::LockTracking::kEnabled) {
70       AddToLocksHeldOnCurrentThread();
71     }
72     CheckUnheldAndMark();
73   }
74   return rv;
75 }
76 
AssertAcquired() const77 void Lock::AssertAcquired() const {
78   DCHECK_EQ(owning_thread_ref_, PlatformThread::CurrentRef());
79 }
80 
AssertNotHeld() const81 void Lock::AssertNotHeld() const {
82   DCHECK(owning_thread_ref_.is_null());
83 }
84 
CheckHeldAndUnmark()85 void Lock::CheckHeldAndUnmark() {
86   DCHECK_EQ(owning_thread_ref_, PlatformThread::CurrentRef());
87   owning_thread_ref_ = PlatformThreadRef();
88 }
89 
CheckUnheldAndMark()90 void Lock::CheckUnheldAndMark() {
91   DCHECK(owning_thread_ref_.is_null());
92   owning_thread_ref_ = PlatformThread::CurrentRef();
93 }
94 
AddToLocksHeldOnCurrentThread()95 void Lock::AddToLocksHeldOnCurrentThread() {
96   CHECK(!in_tracked_locks_held_by_current_thread_);
97 
98   // Check if capacity is exceeded.
99   CHECK_LT(g_num_tracked_locks_held_by_thread, kHeldLocksCapacity)
100       << "This thread holds more than " << kHeldLocksCapacity
101       << " tracked locks simultaneously. Reach out to //base OWNERS to "
102          "determine whether `kHeldLocksCapacity` should be increased.";
103 
104   // Add to the list of held locks.
105   g_tracked_locks_held_by_thread[g_num_tracked_locks_held_by_thread] =
106       reinterpret_cast<uintptr_t>(this);
107   ++g_num_tracked_locks_held_by_thread;
108   in_tracked_locks_held_by_current_thread_ = true;
109 }
110 
RemoveFromLocksHeldOnCurrentThread()111 void Lock::RemoveFromLocksHeldOnCurrentThread() {
112   CHECK(in_tracked_locks_held_by_current_thread_);
113   for (size_t i = 0; i < g_num_tracked_locks_held_by_thread; ++i) {
114     // Traverse from the end since locks are typically acquired and released in
115     // opposite order.
116     const size_t index = g_num_tracked_locks_held_by_thread - i - 1;
117     if (g_tracked_locks_held_by_thread[index] ==
118         reinterpret_cast<uintptr_t>(this)) {
119       g_tracked_locks_held_by_thread[index] =
120           g_tracked_locks_held_by_thread[g_num_tracked_locks_held_by_thread -
121                                          1];
122       g_tracked_locks_held_by_thread[g_num_tracked_locks_held_by_thread - 1] =
123           reinterpret_cast<uintptr_t>(nullptr);
124       --g_num_tracked_locks_held_by_thread;
125       break;
126     }
127   }
128   in_tracked_locks_held_by_current_thread_ = false;
129 }
130 
131 namespace subtle {
132 
GetTrackedLocksHeldByCurrentThread()133 span<const uintptr_t> GetTrackedLocksHeldByCurrentThread() {
134   return span<const uintptr_t>(g_tracked_locks_held_by_thread.begin(),
135                                g_num_tracked_locks_held_by_thread);
136 }
137 
138 }  // namespace subtle
139 
140 }  // namespace base
141 
142 #endif  // DCHECK_IS_ON()
143