// 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 #include #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 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 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 guard = ScopedSpinGuard::TryCreateScopedSpinGuard(kLockThreadTimeoutNanos, s); EXPECT_NE(std::nullopt, guard); EXPECT_TRUE(s.locked); unlock_thread.join(); } TEST(ScopedSpinGuard, SwapShouldMaintainSpinLock) { SpinGuardState s; std::optional outer_guard; EXPECT_EQ(std::nullopt, outer_guard); { std::optional 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 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