Coverage Report

Created: 2026-10-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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