/src/abseil-cpp/absl/base/internal/spinlock.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2017 The Abseil Authors. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include "absl/base/internal/spinlock.h" |
16 | | |
17 | | #include <algorithm> |
18 | | #include <atomic> |
19 | | #include <limits> |
20 | | |
21 | | #include "absl/base/attributes.h" |
22 | | #include "absl/base/config.h" |
23 | | #include "absl/base/internal/atomic_hook.h" |
24 | | #include "absl/base/internal/cycleclock.h" |
25 | | #include "absl/base/internal/spinlock_wait.h" |
26 | | #include "absl/base/internal/sysinfo.h" /* For NumCPUs() */ |
27 | | #include "absl/base/call_once.h" |
28 | | |
29 | | // Description of lock-word: |
30 | | // 31..00: [............................3][2][1][0] |
31 | | // |
32 | | // [0]: kSpinLockHeld |
33 | | // [1]: kSpinLockCooperative |
34 | | // [2]: kSpinLockDisabledScheduling |
35 | | // [31..3]: ONLY kSpinLockSleeper OR |
36 | | // Wait time in cycles >> PROFILE_TIMESTAMP_SHIFT |
37 | | // |
38 | | // Detailed descriptions: |
39 | | // |
40 | | // Bit [0]: The lock is considered held iff kSpinLockHeld is set. |
41 | | // |
42 | | // Bit [1]: Eligible waiters (e.g. Fibers) may co-operatively reschedule when |
43 | | // contended iff kSpinLockCooperative is set. |
44 | | // |
45 | | // Bit [2]: This bit is exclusive from bit [1]. It is used only by a |
46 | | // non-cooperative lock. When set, indicates that scheduling was |
47 | | // successfully disabled when the lock was acquired. May be unset, |
48 | | // even if non-cooperative, if a ThreadIdentity did not yet exist at |
49 | | // time of acquisition. |
50 | | // |
51 | | // Bit [3]: If this is the only upper bit ([31..3]) set then this lock was |
52 | | // acquired without contention, however, at least one waiter exists. |
53 | | // |
54 | | // Otherwise, bits [31..3] represent the time spent by the current lock |
55 | | // holder to acquire the lock. There may be outstanding waiter(s). |
56 | | |
57 | | namespace absl { |
58 | | ABSL_NAMESPACE_BEGIN |
59 | | namespace base_internal { |
60 | | |
61 | | ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES static base_internal::AtomicHook<void (*)( |
62 | | const void *lock, int64_t wait_cycles)> |
63 | | submit_profile_data; |
64 | | |
65 | | void RegisterSpinLockProfiler(void (*fn)(const void *contendedlock, |
66 | 0 | int64_t wait_cycles)) { |
67 | 0 | submit_profile_data.Store(fn); |
68 | 0 | } |
69 | | |
70 | | #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL |
71 | | // Static member variable definitions. |
72 | | constexpr uint32_t SpinLock::kSpinLockHeld; |
73 | | constexpr uint32_t SpinLock::kSpinLockCooperative; |
74 | | constexpr uint32_t SpinLock::kSpinLockDisabledScheduling; |
75 | | constexpr uint32_t SpinLock::kSpinLockSleeper; |
76 | | constexpr uint32_t SpinLock::kWaitTimeMask; |
77 | | #endif |
78 | | |
79 | | // Uncommon constructors. |
80 | | SpinLock::SpinLock(base_internal::SchedulingMode mode) |
81 | 8 | : lockword_(IsCooperative(mode) ? kSpinLockCooperative : 0) { |
82 | 8 | ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static); |
83 | 8 | } |
84 | | |
85 | | // Monitor the lock to see if its value changes within some time period |
86 | | // (adaptive_spin_count loop iterations). The last value read from the lock |
87 | | // is returned from the method. |
88 | 0 | uint32_t SpinLock::SpinLoop() { |
89 | | // We are already in the slow path of SpinLock, initialize the |
90 | | // adaptive_spin_count here. |
91 | 0 | ABSL_CONST_INIT static absl::once_flag init_adaptive_spin_count; |
92 | 0 | ABSL_CONST_INIT static int adaptive_spin_count = 0; |
93 | 0 | base_internal::LowLevelCallOnce(&init_adaptive_spin_count, []() { |
94 | 0 | adaptive_spin_count = base_internal::NumCPUs() > 1 ? 1000 : 1; |
95 | 0 | }); |
96 | |
|
97 | 0 | int c = adaptive_spin_count; |
98 | 0 | uint32_t lock_value; |
99 | 0 | do { |
100 | 0 | lock_value = lockword_.load(std::memory_order_relaxed); |
101 | 0 | } while ((lock_value & kSpinLockHeld) != 0 && --c > 0); |
102 | 0 | return lock_value; |
103 | 0 | } |
104 | | |
105 | 0 | void SpinLock::SlowLock() { |
106 | 0 | uint32_t lock_value = SpinLoop(); |
107 | 0 | lock_value = TryLockInternal(lock_value, 0); |
108 | 0 | if ((lock_value & kSpinLockHeld) == 0) { |
109 | 0 | return; |
110 | 0 | } |
111 | | |
112 | 0 | base_internal::SchedulingMode scheduling_mode; |
113 | 0 | if ((lock_value & kSpinLockCooperative) != 0) { |
114 | 0 | scheduling_mode = base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL; |
115 | 0 | } else { |
116 | 0 | scheduling_mode = base_internal::SCHEDULE_KERNEL_ONLY; |
117 | 0 | } |
118 | | |
119 | | // The lock was not obtained initially, so this thread needs to wait for |
120 | | // it. Record the current timestamp in the local variable wait_start_time |
121 | | // so the total wait time can be stored in the lockword once this thread |
122 | | // obtains the lock. |
123 | 0 | int64_t wait_start_time = CycleClock::Now(); |
124 | 0 | uint32_t wait_cycles = 0; |
125 | 0 | int lock_wait_call_count = 0; |
126 | 0 | while ((lock_value & kSpinLockHeld) != 0) { |
127 | | // If the lock is currently held, but not marked as having a sleeper, mark |
128 | | // it as having a sleeper. |
129 | 0 | if ((lock_value & kWaitTimeMask) == 0) { |
130 | | // Here, just "mark" that the thread is going to sleep. Don't store the |
131 | | // lock wait time in the lock -- the lock word stores the amount of time |
132 | | // that the current holder waited before acquiring the lock, not the wait |
133 | | // time of any thread currently waiting to acquire it. |
134 | 0 | if (lockword_.compare_exchange_strong( |
135 | 0 | lock_value, lock_value | kSpinLockSleeper, |
136 | 0 | std::memory_order_relaxed, std::memory_order_relaxed)) { |
137 | | // Successfully transitioned to kSpinLockSleeper. Pass |
138 | | // kSpinLockSleeper to the SpinLockWait routine to properly indicate |
139 | | // the last lock_value observed. |
140 | 0 | lock_value |= kSpinLockSleeper; |
141 | 0 | } else if ((lock_value & kSpinLockHeld) == 0) { |
142 | | // Lock is free again, so try and acquire it before sleeping. The |
143 | | // new lock state will be the number of cycles this thread waited if |
144 | | // this thread obtains the lock. |
145 | 0 | lock_value = TryLockInternal(lock_value, wait_cycles); |
146 | 0 | continue; // Skip the delay at the end of the loop. |
147 | 0 | } else if ((lock_value & kWaitTimeMask) == 0) { |
148 | | // The lock is still held, without a waiter being marked, but something |
149 | | // else about the lock word changed, causing our CAS to fail. For |
150 | | // example, a new lock holder may have acquired the lock with |
151 | | // kSpinLockDisabledScheduling set, whereas the previous holder had not |
152 | | // set that flag. In this case, attempt again to mark ourselves as a |
153 | | // waiter. |
154 | 0 | continue; |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | // SpinLockDelay() calls into fiber scheduler, we need to see |
159 | | // synchronization there to avoid false positives. |
160 | 0 | ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0); |
161 | | // Wait for an OS specific delay. |
162 | 0 | base_internal::SpinLockDelay(&lockword_, lock_value, ++lock_wait_call_count, |
163 | 0 | scheduling_mode); |
164 | 0 | ABSL_TSAN_MUTEX_POST_DIVERT(this, 0); |
165 | | // Spin again after returning from the wait routine to give this thread |
166 | | // some chance of obtaining the lock. |
167 | 0 | lock_value = SpinLoop(); |
168 | 0 | wait_cycles = EncodeWaitCycles(wait_start_time, CycleClock::Now()); |
169 | 0 | lock_value = TryLockInternal(lock_value, wait_cycles); |
170 | 0 | } |
171 | 0 | } |
172 | | |
173 | 0 | void SpinLock::SlowUnlock(uint32_t lock_value) { |
174 | 0 | base_internal::SpinLockWake(&lockword_, |
175 | 0 | false); // wake waiter if necessary |
176 | | |
177 | | // If our acquisition was contended, collect contentionz profile info. We |
178 | | // reserve a unitary wait time to represent that a waiter exists without our |
179 | | // own acquisition having been contended. |
180 | 0 | if ((lock_value & kWaitTimeMask) != kSpinLockSleeper) { |
181 | 0 | const int64_t wait_cycles = DecodeWaitCycles(lock_value); |
182 | 0 | ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0); |
183 | 0 | submit_profile_data(this, wait_cycles); |
184 | 0 | ABSL_TSAN_MUTEX_POST_DIVERT(this, 0); |
185 | 0 | } |
186 | 0 | } |
187 | | |
188 | | // We use the upper 29 bits of the lock word to store the time spent waiting to |
189 | | // acquire this lock. This is reported by contentionz profiling. Since the |
190 | | // lower bits of the cycle counter wrap very quickly on high-frequency |
191 | | // processors we divide to reduce the granularity to 2^kProfileTimestampShift |
192 | | // sized units. On a 4Ghz machine this will lose track of wait times greater |
193 | | // than (2^29/4 Ghz)*128 =~ 17.2 seconds. Such waits should be extremely rare. |
194 | | static constexpr int kProfileTimestampShift = 7; |
195 | | |
196 | | // We currently reserve the lower 3 bits. |
197 | | static constexpr int kLockwordReservedShift = 3; |
198 | | |
199 | | uint32_t SpinLock::EncodeWaitCycles(int64_t wait_start_time, |
200 | 0 | int64_t wait_end_time) { |
201 | 0 | static const int64_t kMaxWaitTime = |
202 | 0 | std::numeric_limits<uint32_t>::max() >> kLockwordReservedShift; |
203 | 0 | int64_t scaled_wait_time = |
204 | 0 | (wait_end_time - wait_start_time) >> kProfileTimestampShift; |
205 | | |
206 | | // Return a representation of the time spent waiting that can be stored in |
207 | | // the lock word's upper bits. |
208 | 0 | uint32_t clamped = static_cast<uint32_t>( |
209 | 0 | std::min(scaled_wait_time, kMaxWaitTime) << kLockwordReservedShift); |
210 | |
|
211 | 0 | if (clamped == 0) { |
212 | 0 | return kSpinLockSleeper; // Just wake waiters, but don't record contention. |
213 | 0 | } |
214 | | // Bump up value if necessary to avoid returning kSpinLockSleeper. |
215 | 0 | const uint32_t kMinWaitTime = |
216 | 0 | kSpinLockSleeper + (1 << kLockwordReservedShift); |
217 | 0 | if (clamped == kSpinLockSleeper) { |
218 | 0 | return kMinWaitTime; |
219 | 0 | } |
220 | 0 | return clamped; |
221 | 0 | } |
222 | | |
223 | 0 | int64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) { |
224 | | // Cast to uint32_t first to ensure bits [63:32] are cleared. |
225 | 0 | const int64_t scaled_wait_time = |
226 | 0 | static_cast<uint32_t>(lock_value & kWaitTimeMask); |
227 | 0 | return scaled_wait_time << (kProfileTimestampShift - kLockwordReservedShift); |
228 | 0 | } |
229 | | |
230 | | } // namespace base_internal |
231 | | ABSL_NAMESPACE_END |
232 | | } // namespace absl |