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