/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  |  |  | 
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  |