CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
228 lines
7.0 KiB
C++
228 lines
7.0 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Utility/Locked.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
|
|
using namespace lldb_private;
|
|
|
|
namespace {
|
|
struct Widget {
|
|
int value = 0;
|
|
};
|
|
} // namespace
|
|
|
|
// Default mutex types match the LLDB conventions: recursive_mutex for write
|
|
// access, llvm::sys::RWMutex for read access.
|
|
static_assert(
|
|
std::is_same_v<LockedPtr<Widget>::mutex_type, std::recursive_mutex>);
|
|
static_assert(
|
|
std::is_same_v<LockedSP<Widget>::mutex_type, std::recursive_mutex>);
|
|
static_assert(
|
|
std::is_same_v<LockedUP<Widget>::mutex_type, std::recursive_mutex>);
|
|
static_assert(
|
|
std::is_same_v<SharedLockedPtr<Widget>::mutex_type, llvm::sys::RWMutex>);
|
|
static_assert(
|
|
std::is_same_v<SharedLockedSP<Widget>::mutex_type, llvm::sys::RWMutex>);
|
|
static_assert(
|
|
std::is_same_v<SharedLockedUP<Widget>::mutex_type, llvm::sys::RWMutex>);
|
|
|
|
// Force compile-time validation of every (pointer-flavor x mutex) combination
|
|
// the templates are intended to support.
|
|
template class lldb_private::Locked<Widget *, std::mutex>;
|
|
template class lldb_private::Locked<Widget *, std::recursive_mutex>;
|
|
template class lldb_private::Locked<Widget *, llvm::sys::RWMutex>;
|
|
template class lldb_private::Locked<std::shared_ptr<Widget>,
|
|
std::recursive_mutex>;
|
|
template class lldb_private::Locked<std::unique_ptr<Widget>,
|
|
std::recursive_mutex>;
|
|
template class lldb_private::SharedLocked<const Widget *, std::shared_mutex>;
|
|
template class lldb_private::SharedLocked<const Widget *, llvm::sys::RWMutex>;
|
|
template class lldb_private::SharedLocked<std::shared_ptr<const Widget>,
|
|
llvm::sys::RWMutex>;
|
|
template class lldb_private::SharedLocked<std::unique_ptr<const Widget>,
|
|
llvm::sys::RWMutex>;
|
|
|
|
// Locked is move-only; SharedLocked is copyable.
|
|
static_assert(!std::is_copy_constructible_v<LockedPtr<Widget>>);
|
|
static_assert(!std::is_copy_assignable_v<LockedPtr<Widget>>);
|
|
static_assert(std::is_move_constructible_v<LockedPtr<Widget>>);
|
|
static_assert(std::is_move_assignable_v<LockedPtr<Widget>>);
|
|
|
|
static_assert(std::is_copy_constructible_v<SharedLockedPtr<Widget>>);
|
|
static_assert(std::is_copy_assignable_v<SharedLockedPtr<Widget>>);
|
|
static_assert(std::is_move_constructible_v<SharedLockedPtr<Widget>>);
|
|
static_assert(std::is_move_assignable_v<SharedLockedPtr<Widget>>);
|
|
|
|
TEST(LockedTest, DefaultConstructed) {
|
|
LockedPtr<Widget> handle;
|
|
EXPECT_FALSE(handle);
|
|
EXPECT_EQ(handle.get(), nullptr);
|
|
|
|
SharedLockedPtr<Widget> shared;
|
|
EXPECT_FALSE(shared);
|
|
EXPECT_EQ(shared.get(), nullptr);
|
|
}
|
|
|
|
TEST(LockedTest, ExclusivePtrAccess) {
|
|
std::recursive_mutex mutex;
|
|
Widget widget;
|
|
|
|
LockedPtr<Widget> handle(mutex, &widget);
|
|
ASSERT_TRUE(handle);
|
|
EXPECT_EQ(handle.get(), &widget);
|
|
|
|
// A different thread cannot acquire the mutex while the handle holds it.
|
|
std::thread other([&] { EXPECT_FALSE(mutex.try_lock()); });
|
|
other.join();
|
|
|
|
handle->value = 7;
|
|
EXPECT_EQ((*handle).value, 7);
|
|
}
|
|
|
|
TEST(LockedTest, ExclusiveReleasesOnDestruction) {
|
|
std::mutex mutex;
|
|
Widget widget;
|
|
{
|
|
LockedPtr<Widget, std::mutex> handle(mutex, &widget);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
}
|
|
if (mutex.try_lock())
|
|
mutex.unlock();
|
|
else
|
|
ADD_FAILURE() << "Mutex was not released";
|
|
}
|
|
|
|
TEST(LockedTest, MoveTransfersLock) {
|
|
std::mutex mutex;
|
|
Widget widget;
|
|
|
|
LockedPtr<Widget, std::mutex> first(mutex, &widget);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
|
|
LockedPtr<Widget, std::mutex> second = std::move(first);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
EXPECT_TRUE(second);
|
|
EXPECT_EQ(second.get(), &widget);
|
|
|
|
// The moved-from handle no longer points anywhere — the raw pointer must
|
|
// be nulled out so operator bool can't claim ownership of a lock the
|
|
// handle no longer holds.
|
|
EXPECT_FALSE(first);
|
|
EXPECT_EQ(first.get(), nullptr);
|
|
}
|
|
|
|
TEST(LockedTest, AcceptsExternallyAcquiredLock) {
|
|
std::mutex mutex;
|
|
Widget widget;
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
LockedPtr<Widget, std::mutex> handle(std::move(lock), &widget);
|
|
ASSERT_TRUE(handle);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
}
|
|
|
|
TEST(LockedTest, SharedPtrFlavor) {
|
|
std::recursive_mutex mutex;
|
|
auto widget_sp = std::make_shared<Widget>();
|
|
widget_sp->value = 42;
|
|
|
|
LockedSP<Widget> handle(mutex, widget_sp);
|
|
ASSERT_TRUE(handle);
|
|
EXPECT_EQ(handle.get(), widget_sp.get());
|
|
EXPECT_EQ(handle->value, 42);
|
|
}
|
|
|
|
TEST(LockedTest, UniquePtrFlavor) {
|
|
std::recursive_mutex mutex;
|
|
auto widget_up = std::make_unique<Widget>();
|
|
widget_up->value = 99;
|
|
Widget *raw = widget_up.get();
|
|
|
|
LockedUP<Widget> handle(mutex, std::move(widget_up));
|
|
ASSERT_TRUE(handle);
|
|
EXPECT_EQ(handle.get(), raw);
|
|
EXPECT_EQ(handle->value, 99);
|
|
}
|
|
|
|
TEST(LockedTest, SharedAccessOnRWMutex) {
|
|
llvm::sys::RWMutex mutex;
|
|
Widget widget;
|
|
widget.value = 5;
|
|
|
|
SharedLockedPtr<Widget> reader(mutex, &widget);
|
|
ASSERT_TRUE(reader);
|
|
EXPECT_EQ(reader->value, 5);
|
|
|
|
static_assert(std::is_same_v<decltype(reader.get()), const Widget *>,
|
|
"shared access borrows a const-qualified pointer");
|
|
}
|
|
|
|
// Copies of a SharedLocked share the same reader lock; the lock is released
|
|
// only when the last copy goes away. Verified by polling try_lock() — it
|
|
// must keep failing while any copy is alive.
|
|
TEST(LockedTest, SharedAccessIsRefCounted) {
|
|
llvm::sys::RWMutex mutex;
|
|
Widget widget;
|
|
|
|
std::optional<SharedLockedPtr<Widget>> first;
|
|
first.emplace(mutex, &widget);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
|
|
std::optional<SharedLockedPtr<Widget>> second = first;
|
|
ASSERT_TRUE(second);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
|
|
first.reset();
|
|
// Second copy still holds the reader lock.
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
|
|
second.reset();
|
|
// All copies gone — the writer lock is now obtainable.
|
|
EXPECT_TRUE(mutex.try_lock());
|
|
mutex.unlock();
|
|
}
|
|
|
|
TEST(LockedTest, SharedAcceptsExternallyAcquiredLock) {
|
|
llvm::sys::RWMutex mutex;
|
|
Widget widget;
|
|
|
|
std::shared_lock<llvm::sys::RWMutex> lock(mutex);
|
|
SharedLockedPtr<Widget> handle(std::move(lock), &widget);
|
|
ASSERT_TRUE(handle);
|
|
EXPECT_FALSE(mutex.try_lock());
|
|
}
|
|
|
|
TEST(LockedTest, SharedMoveNullsSource) {
|
|
llvm::sys::RWMutex mutex;
|
|
Widget widget;
|
|
|
|
SharedLockedPtr<Widget> first(mutex, &widget);
|
|
SharedLockedPtr<Widget> second = std::move(first);
|
|
EXPECT_TRUE(second);
|
|
EXPECT_EQ(second.get(), &widget);
|
|
EXPECT_FALSE(first);
|
|
EXPECT_EQ(first.get(), nullptr);
|
|
}
|
|
|
|
TEST(LockedTest, ExclusiveAccessOnRWMutex) {
|
|
llvm::sys::RWMutex mutex;
|
|
Widget widget;
|
|
LockedPtr<Widget, llvm::sys::RWMutex> writer(mutex, &widget);
|
|
ASSERT_TRUE(writer);
|
|
writer->value = 11;
|
|
EXPECT_EQ(widget.value, 11);
|
|
}
|