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