mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
[util] New class ScopedSpinGuard
To support a new crashpad::RingBufferAnnotation type which can be safely written to and read from simultaneously by different threads/processes, this CL introduces a new class ScopedSpinGuard, which is a simple RAII wrapper around an atomic boolean. Change-Id: I5bafe6927a8dc2a3e25734cb941fd9fce9a8d139 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4031729 Commit-Queue: Ben Hamilton <benhamilton@google.com> Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
parent
5a8a43a992
commit
f7b5e00268
@ -267,6 +267,7 @@ crashpad_static_library("util") {
|
|||||||
"stream/zlib_output_stream.h",
|
"stream/zlib_output_stream.h",
|
||||||
"string/split_string.cc",
|
"string/split_string.cc",
|
||||||
"string/split_string.h",
|
"string/split_string.h",
|
||||||
|
"synchronization/scoped_spin_guard.h",
|
||||||
"synchronization/semaphore.h",
|
"synchronization/semaphore.h",
|
||||||
"thread/stoppable.h",
|
"thread/stoppable.h",
|
||||||
"thread/thread.cc",
|
"thread/thread.cc",
|
||||||
@ -775,6 +776,7 @@ source_set("util_test") {
|
|||||||
"stream/test_output_stream.h",
|
"stream/test_output_stream.h",
|
||||||
"stream/zlib_output_stream_test.cc",
|
"stream/zlib_output_stream_test.cc",
|
||||||
"string/split_string_test.cc",
|
"string/split_string_test.cc",
|
||||||
|
"synchronization/scoped_spin_guard_test.cc",
|
||||||
"synchronization/semaphore_test.cc",
|
"synchronization/semaphore_test.cc",
|
||||||
"thread/thread_log_messages_test.cc",
|
"thread/thread_log_messages_test.cc",
|
||||||
"thread/thread_test.cc",
|
"thread/thread_test.cc",
|
||||||
|
122
util/synchronization/scoped_spin_guard.h
Normal file
122
util/synchronization/scoped_spin_guard.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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 <atomic>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#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<bool> locked;
|
||||||
|
static_assert(std::atomic<bool>::is_always_lock_free,
|
||||||
|
"std::atomic<bool> may not be signal-safe");
|
||||||
|
static_assert(sizeof(std::atomic<bool>) == sizeof(bool),
|
||||||
|
"std::atomic<bool> 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<ScopedSpinGuard> 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<ScopedSpinGuard>(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_
|
109
util/synchronization/scoped_spin_guard_test.cc
Normal file
109
util/synchronization/scoped_spin_guard_test.cc
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#include "util/synchronization/scoped_spin_guard.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "util/misc/clock.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(ScopedSpinGuard, TryCreateScopedSpinGuardShouldLockStateWhileInScope) {
|
||||||
|
SpinGuardState s;
|
||||||
|
EXPECT_FALSE(s.locked);
|
||||||
|
{
|
||||||
|
std::optional<ScopedSpinGuard> guard =
|
||||||
|
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||||
|
EXPECT_NE(std::nullopt, guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(s.locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(
|
||||||
|
ScopedSpinGuard,
|
||||||
|
TryCreateScopedSpinGuardWithZeroTimeoutShouldFailImmediatelyIfStateLocked) {
|
||||||
|
SpinGuardState s;
|
||||||
|
s.locked = true;
|
||||||
|
std::optional<ScopedSpinGuard> guard =
|
||||||
|
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||||
|
EXPECT_EQ(std::nullopt, guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(
|
||||||
|
ScopedSpinGuard,
|
||||||
|
TryCreateScopedSpinGuardWithNonZeroTimeoutShouldSucceedIfStateUnlockedDuringTimeout) {
|
||||||
|
SpinGuardState s;
|
||||||
|
s.locked = true;
|
||||||
|
std::thread unlock_thread([&s] {
|
||||||
|
constexpr uint64_t kUnlockThreadSleepTimeNanos = 10000; // 10 us
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
SleepNanoseconds(kUnlockThreadSleepTimeNanos);
|
||||||
|
s.locked = false;
|
||||||
|
});
|
||||||
|
constexpr uint64_t kLockThreadTimeoutNanos = 180000000000ULL; // 3 minutes
|
||||||
|
std::optional<ScopedSpinGuard> guard =
|
||||||
|
ScopedSpinGuard::TryCreateScopedSpinGuard(kLockThreadTimeoutNanos, s);
|
||||||
|
EXPECT_NE(std::nullopt, guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
unlock_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScopedSpinGuard, SwapShouldMaintainSpinLock) {
|
||||||
|
SpinGuardState s;
|
||||||
|
std::optional<ScopedSpinGuard> outer_guard;
|
||||||
|
EXPECT_EQ(std::nullopt, outer_guard);
|
||||||
|
{
|
||||||
|
std::optional<ScopedSpinGuard> inner_guard =
|
||||||
|
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||||
|
EXPECT_NE(std::nullopt, inner_guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
// If the move-assignment operator for `ScopedSpinGuard` is implemented
|
||||||
|
// incorrectly (e.g., the `= default` implementation), `inner_guard`
|
||||||
|
// will incorrectly think it still "owns" the spinlock after the swap,
|
||||||
|
// and when it falls out of scope, it will release the lock prematurely
|
||||||
|
// when it destructs.
|
||||||
|
//
|
||||||
|
// Confirm that the spinlock stays locked even after the swap.
|
||||||
|
std::swap(outer_guard, inner_guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
EXPECT_EQ(std::nullopt, inner_guard);
|
||||||
|
}
|
||||||
|
EXPECT_NE(std::nullopt, outer_guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScopedSpinGuard, MoveAssignmentShouldMaintainSpinLock) {
|
||||||
|
SpinGuardState s;
|
||||||
|
std::optional<ScopedSpinGuard> outer_guard;
|
||||||
|
EXPECT_EQ(std::nullopt, outer_guard);
|
||||||
|
{
|
||||||
|
outer_guard =
|
||||||
|
ScopedSpinGuard::TryCreateScopedSpinGuard(/*timeout_nanos=*/0, s);
|
||||||
|
EXPECT_NE(std::nullopt, outer_guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
}
|
||||||
|
EXPECT_NE(std::nullopt, outer_guard);
|
||||||
|
EXPECT_TRUE(s.locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
Loading…
x
Reference in New Issue
Block a user