// Copyright 2022 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_UTIL_SYNCHRONIZATION_SCOPED_SPIN_GUARD_H_ #define CRASHPAD_UTIL_SYNCHRONIZATION_SCOPED_SPIN_GUARD_H_ #include #include #include #include "base/check.h" #include "base/notreached.h" #include "util/misc/clock.h" namespace crashpad { //! \brief Spinlock state for `ScopedSpinGuard`. struct SpinGuardState final { //! \brief A `ScopedSpinGuard` in an unlocked state. constexpr SpinGuardState() : locked(false) {} SpinGuardState(const SpinGuardState&) = delete; SpinGuardState& operator=(const SpinGuardState&) = delete; //! \brief `true` if the `ScopedSpinGuard` is locked, `false` otherwise. std::atomic locked; static_assert(std::atomic::is_always_lock_free, "std::atomic may not be signal-safe"); static_assert(sizeof(std::atomic) == sizeof(bool), "std::atomic adds size to bool"); }; //! \brief A scoped mutual-exclusion guard wrapping a `SpinGuardState` with RAII //! semantics. class ScopedSpinGuard final { //! \brief The duration in nanoseconds between attempts to lock the spinlock. static constexpr uint64_t kSpinGuardSleepTimeNanos = 10; public: ScopedSpinGuard(const ScopedSpinGuard&) = delete; ScopedSpinGuard& operator=(const ScopedSpinGuard&) = delete; ScopedSpinGuard(ScopedSpinGuard&& other) noexcept : state_(nullptr) { std::swap(state_, other.state_); } ScopedSpinGuard& operator=(ScopedSpinGuard&& other) { std::swap(state_, other.state_); return *this; } //! \brief Spins up to `timeout_nanos` nanoseconds trying to lock `state`. //! \param[in] timeout_nanos The timeout in nanoseconds after which this gives //! up trying to lock the spinlock and returns `std::nullopt`. //! \param[in,out] state The spinlock state to attempt to lock. This method //! holds a pointer to `state`, so `state` must outlive the lifetime of //! this object. //! \return The locked `ScopedSpinGuard` on success, or `std::nullopt` on //! timeout. static std::optional TryCreateScopedSpinGuard( uint64_t timeout_nanos, SpinGuardState& state) { const uint64_t clock_end_time_nanos = ClockMonotonicNanoseconds() + timeout_nanos; while (true) { bool expected_current_value = false; if (state.locked.compare_exchange_weak(expected_current_value, true, std::memory_order_acquire, std::memory_order_relaxed)) { return std::make_optional(state); } if (ClockMonotonicNanoseconds() >= clock_end_time_nanos) { return std::nullopt; } SleepNanoseconds(kSpinGuardSleepTimeNanos); } NOTREACHED(); } ~ScopedSpinGuard() { if (state_) { #ifdef NDEBUG state_->locked.store(false, std::memory_order_release); #else bool old = state_->locked.exchange(false, std::memory_order_release); DCHECK(old); #endif } } //! \brief A `ScopedSpinGuard` wrapping a locked `SpinGuardState`. //! \param[in,out] locked_state A locked `SpinGuardState`. This method //! holds a pointer to `state`, so `state` must outlive the lifetime of //! this object. ScopedSpinGuard(SpinGuardState& locked_state) : state_(&locked_state) { DCHECK(locked_state.locked); } private: // \brief Optional spinlock state, unlocked when this object goes out of // scope. // // If this is `nullptr`, then this object has been moved from, and the state // is no longer valid. In that case, nothing will be unlocked when this object // is destroyed. SpinGuardState* state_; }; } // namespace crashpad #endif // CRASHPAD_UTIL_SYNCHRONIZATION_SCOPED_SPIN_GUARD_H_