tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

spinlock.cc (9202B)


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