From d090ddaabe4add06ea03cbb957b97d02347fff8f Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 19 Jan 2025 11:53:45 -0500 Subject: [PATCH] feat(dobj/LockableDObject): added thread safe implimentation of DObject In order to build a preformant code code base we may want to make parallized code in which case having a lockable DObject is a useful construct --- src/dobj/private/LockableDObject.cpp | 22 +++++++ src/dobj/public/LockableDObject.h | 47 +++++++++++++++ src/meson.build | 8 ++- tests/dobj/LockableDObjectTest.cpp | 89 ++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/dobj/private/LockableDObject.cpp create mode 100644 src/dobj/public/LockableDObject.h create mode 100644 tests/dobj/LockableDObjectTest.cpp diff --git a/src/dobj/private/LockableDObject.cpp b/src/dobj/private/LockableDObject.cpp new file mode 100644 index 0000000..65b8cf9 --- /dev/null +++ b/src/dobj/private/LockableDObject.cpp @@ -0,0 +1,22 @@ +#include "LockableDObject.h" + +/** + * @brief Access the underlying DObject. + */ +DObject& LockableDObject::get() { + return object_; +} + +/** + * @brief Locks the mutex to ensure thread-safe access. + */ +void LockableDObject::lock() { + mutex_.lock(); +} + +/** + * @brief Unlocks the mutex after thread-safe access. + */ +void LockableDObject::unlock() { + mutex_.unlock(); +} diff --git a/src/dobj/public/LockableDObject.h b/src/dobj/public/LockableDObject.h new file mode 100644 index 0000000..60790eb --- /dev/null +++ b/src/dobj/public/LockableDObject.h @@ -0,0 +1,47 @@ +#ifndef LOCKABLE_DOBJECT_H +#define LOCKABLE_DOBJECT_H + +#include "DObject.h" +#include + +/** + * @file LockableDObject.h + * @brief A lightweight wrapper for DObject that adds locking capabilities. + * + * This class allows safe concurrent access to a DObject by providing + * locking and unlocking methods. + */ + +/** + * @class LockableDObject + * @brief Wrapper for DObject with thread-safe access. + */ +class LockableDObject { +public: + /** + * @brief Default constructor. + */ + LockableDObject() = default; + + /** + * @brief Access the underlying DObject. + * @return A reference to the wrapped DObject. + */ + DObject& get(); + + /** + * @brief Locks the mutex to ensure thread-safe access. + */ + void lock(); + + /** + * @brief Unlocks the mutex after thread-safe access. + */ + void unlock(); + +private: + DObject object_; ///< The underlying DObject instance. + std::mutex mutex_; ///< Mutex for thread-safe access. +}; + +#endif // LOCKABLE_DOBJECT_H \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 08e8c91..e97649d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,10 +1,14 @@ # Define the library dobj_sources = files( - 'dobj/private/Metadata.cpp' + 'dobj/private/Metadata.cpp', + 'dobj/private/DObject.cpp', + 'dobj/private/LockableDObject.cpp' ) dobj_headers = files( - 'dobj/public/Metadata.h' + 'dobj/public/Metadata.h', + 'dobj/public/DObject.h', + 'dobj/public/LockableDObject.h' ) # Define the libdobj library so it can be linked against by other parts of the build system diff --git a/tests/dobj/LockableDObjectTest.cpp b/tests/dobj/LockableDObjectTest.cpp new file mode 100644 index 0000000..e2bbc36 --- /dev/null +++ b/tests/dobj/LockableDObjectTest.cpp @@ -0,0 +1,89 @@ +#include +#include "LockableDObject.h" +#include +#include + +/** + * @file LockableDObjectTest.cpp + * @brief Unit tests for the LockableDObject class. + */ + +/** + * @brief Test suite for the LockableDObject class. + */ +class LockableDObjectTest : public ::testing::Test { +protected: + LockableDObject lockableObject; + + void SetUp() override { + // Initialize the LockableDObject for tests + DObject& obj = lockableObject.get(); + obj.setData(42); // Set initial data + } +}; + +/** + * @test Verify the default construction of LockableDObject. + */ +TEST_F(LockableDObjectTest, DefaultConstruction) { + EXPECT_NO_THROW(LockableDObject obj); +} + +/** + * @test Verify access to the underlying DObject. + */ +TEST_F(LockableDObjectTest, AccessDObject) { + DObject& obj = lockableObject.get(); + EXPECT_EQ(std::get(obj.getData()), 42); // Ensure the data is accessible +} + +/** + * @test Verify locking and unlocking. + */ +TEST_F(LockableDObjectTest, LockAndUnlock) { + EXPECT_NO_THROW(lockableObject.lock()); + EXPECT_NO_THROW(lockableObject.unlock()); +} + +/** + * @test Verify thread safety with multiple threads. + */ +TEST_F(LockableDObjectTest, ThreadSafety) { + const int numThreads = 10; + std::vector threads; + std::atomic successCount{0}; + + // Each thread tries to update the data + for (int i = 0; i < numThreads; ++i) { + threads.emplace_back([this, i, &successCount]() { + lockableObject.lock(); + DObject& obj = lockableObject.get(); + obj.setData(i); + if (std::get(obj.getData()) == i) { + successCount++; + } + lockableObject.unlock(); + }); + } + + for (auto& t : threads) { + t.join(); + } + + EXPECT_EQ(successCount, numThreads); +} + +/** + * @test Verify that the DObject remains consistent when accessed via LockableDObject. + */ +TEST_F(LockableDObjectTest, ConsistentState) { + lockableObject.lock(); + DObject& obj = lockableObject.get(); + obj.setData(100); + EXPECT_EQ(std::get(obj.getData()), 100); + lockableObject.unlock(); + + lockableObject.lock(); + EXPECT_EQ(std::get(lockableObject.get().getData()), 100); + lockableObject.unlock(); +}