mirror of
https://github.com/hyprwm/hyprutils.git
synced 2025-12-20 02:20:16 +01:00
memory: add CAtomicSharedPointer and CAtomicWeakPointer (#57)
This commit is contained in:
parent
1b8090e5d8
commit
925f26633f
3 changed files with 456 additions and 2 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -34,6 +34,7 @@
|
||||||
build/
|
build/
|
||||||
.vscode/
|
.vscode/
|
||||||
.cache/
|
.cache/
|
||||||
|
.direnv/
|
||||||
|
|
||||||
.cmake/
|
.cmake/
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
|
|
@ -44,3 +45,4 @@ Makefile
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
hyprutils.pc
|
hyprutils.pc
|
||||||
|
.envrc
|
||||||
|
|
|
||||||
364
include/hyprutils/memory/Atomic.hpp
Normal file
364
include/hyprutils/memory/Atomic.hpp
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "./ImplBase.hpp"
|
||||||
|
#include "./SharedPtr.hpp"
|
||||||
|
#include "./WeakPtr.hpp"
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
/*
|
||||||
|
This header provides a thread-safe wrapper for Hyprutils shared pointer implementations.
|
||||||
|
Like with STL shared pointers, that does not mean that individual SP/WP objects can be shared across threads without synchronization.
|
||||||
|
It only means that the refcounting of the data is thread-safe.
|
||||||
|
|
||||||
|
Should an Atomic SP/WP be shared across threads, calling a non-const member leads to a data race.
|
||||||
|
To avoid that, each thread should have thread-local SP/WP objects.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
We have a CAtomicSharedPointer member in a class. Suppose this member is accessed by multiple threads and is not constant.
|
||||||
|
In such a case we need external synchronization to ensure valid data access.
|
||||||
|
However, if we create a copy of this CAtomicWeakPointer member for each thread that accesses it,
|
||||||
|
then the references to the object will be counted in a thread-safe manner and it will be safe to lock a WP and to access the data in case of an SP.
|
||||||
|
In such an example, the inner data would need its own synchronization mechanism if it isn't constant itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyprutils::Memory {
|
||||||
|
namespace Atomic_ {
|
||||||
|
template <typename T>
|
||||||
|
class impl : public Impl_::impl<T> {
|
||||||
|
std::recursive_mutex m_mutex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
impl(T* data, bool lock = true) noexcept : Impl_::impl<T>(data, lock) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::recursive_mutex> lockGuard() {
|
||||||
|
return std::lock_guard<std::recursive_mutex>(m_mutex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward declaration for friend
|
||||||
|
template <typename T>
|
||||||
|
class CAtomicWeakPointer;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class CAtomicSharedPointer {
|
||||||
|
template <typename X>
|
||||||
|
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
|
||||||
|
template <typename X>
|
||||||
|
using validHierarchy = typename std::enable_if<std::is_assignable<CAtomicSharedPointer<T>&, X>::value, CAtomicSharedPointer&>::type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CAtomicSharedPointer(Impl_::impl_base* impl) noexcept : m_ptr(impl) {}
|
||||||
|
|
||||||
|
CAtomicSharedPointer(const CAtomicSharedPointer<T>& ref) {
|
||||||
|
if (!ref.m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = ref.implLockGuard();
|
||||||
|
m_ptr = ref.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CAtomicSharedPointer(const CAtomicSharedPointer<U>& ref) {
|
||||||
|
if (!ref.m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = ref.implLockGuard();
|
||||||
|
m_ptr = ref.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CAtomicSharedPointer(CAtomicSharedPointer<U>&& ref) noexcept {
|
||||||
|
std::swap(m_ptr, ref.m_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicSharedPointer(CAtomicSharedPointer&& ref) noexcept {
|
||||||
|
std::swap(m_ptr, ref.m_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicSharedPointer() noexcept {
|
||||||
|
; // empty
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicSharedPointer(std::nullptr_t) noexcept {
|
||||||
|
; // empty
|
||||||
|
}
|
||||||
|
|
||||||
|
~CAtomicSharedPointer() {
|
||||||
|
if (!m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CAtomicSharedPointer<U>&> operator=(const CAtomicSharedPointer<U>& rhs) {
|
||||||
|
if (m_ptr.impl_) {
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rhs.m_ptr.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
auto lg = rhs.implLockGuard();
|
||||||
|
m_ptr = rhs.m_ptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicSharedPointer& operator=(const CAtomicSharedPointer& rhs) {
|
||||||
|
if (this == &rhs)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
if (m_ptr.impl_) {
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rhs.m_ptr.impl_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
auto lg = rhs.implLockGuard();
|
||||||
|
m_ptr = rhs.m_ptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CAtomicSharedPointer<U>&> operator=(CAtomicSharedPointer<U>&& rhs) noexcept {
|
||||||
|
std::swap(m_ptr, rhs.m_ptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicSharedPointer& operator=(CAtomicSharedPointer&& rhs) noexcept {
|
||||||
|
if (this == &rhs)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
std::swap(m_ptr, rhs.m_ptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
if (!m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator*() const {
|
||||||
|
return *m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator->() const {
|
||||||
|
return m_ptr.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
T* get() const {
|
||||||
|
return m_ptr.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CAtomicSharedPointer& rhs) const {
|
||||||
|
return m_ptr == rhs.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const CAtomicSharedPointer& lhs, const CAtomicSharedPointer& rhs) const {
|
||||||
|
return lhs.m_ptr == rhs.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int strongRef() const {
|
||||||
|
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
||||||
|
return ((Atomic_::impl<T>*)m_ptr.impl_)->lockGuard();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharedPointer<T> m_ptr;
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
friend class CAtomicWeakPointer;
|
||||||
|
template <typename U>
|
||||||
|
friend class CAtomicSharedPointer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class CAtomicWeakPointer {
|
||||||
|
|
||||||
|
template <typename X>
|
||||||
|
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
|
||||||
|
template <typename X>
|
||||||
|
using validHierarchy = typename std::enable_if<std::is_assignable<CAtomicWeakPointer<T>&, X>::value, CAtomicWeakPointer&>::type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CAtomicWeakPointer(const CAtomicWeakPointer<T>& ref) {
|
||||||
|
if (!ref.m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = ref.implLockGuard();
|
||||||
|
m_ptr = ref.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CAtomicWeakPointer(const CAtomicWeakPointer<U>& ref) {
|
||||||
|
if (!ref.m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = ref.implLockGuard();
|
||||||
|
m_ptr = ref.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename = isConstructible<U>>
|
||||||
|
CAtomicWeakPointer(CAtomicWeakPointer<U>&& ref) noexcept {
|
||||||
|
std::swap(m_ptr, ref.m_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicWeakPointer(CAtomicWeakPointer&& ref) noexcept {
|
||||||
|
std::swap(m_ptr, ref.m_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicWeakPointer(const CAtomicSharedPointer<T>& ref) {
|
||||||
|
if (!ref.m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = ref.implLockGuard();
|
||||||
|
m_ptr = ref.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicWeakPointer() noexcept {
|
||||||
|
; // empty
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicWeakPointer(std::nullptr_t) noexcept {
|
||||||
|
; // empty
|
||||||
|
}
|
||||||
|
|
||||||
|
~CAtomicWeakPointer() {
|
||||||
|
if (!m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CAtomicWeakPointer<U>&> operator=(const CAtomicWeakPointer<U>& rhs) {
|
||||||
|
if (m_ptr.impl_) {
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lg = rhs.implLockGuard();
|
||||||
|
m_ptr = rhs.m_ptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicWeakPointer& operator=(const CAtomicWeakPointer& rhs) {
|
||||||
|
if (this == &rhs)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
if (m_ptr.impl_) {
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lg = rhs.implLockGuard();
|
||||||
|
m_ptr = rhs.m_ptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
validHierarchy<const CAtomicWeakPointer<U>&> operator=(CAtomicWeakPointer<U>&& rhs) noexcept {
|
||||||
|
std::swap(m_ptr, rhs.m_ptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicWeakPointer& operator=(CAtomicWeakPointer&& rhs) noexcept {
|
||||||
|
if (this == &rhs)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
std::swap(m_ptr, rhs.m_ptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
if (!m_ptr.impl_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
m_ptr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
T& operator*() const {
|
||||||
|
return *m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator->() const {
|
||||||
|
return m_ptr.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
T* get() const {
|
||||||
|
return m_ptr.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CAtomicWeakPointer& rhs) const {
|
||||||
|
return m_ptr == rhs.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CAtomicSharedPointer<T>& rhs) const {
|
||||||
|
return m_ptr == rhs.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const CAtomicWeakPointer& lhs, const CAtomicWeakPointer& rhs) const {
|
||||||
|
return lhs.m_ptr == rhs.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expired() {
|
||||||
|
return m_ptr.expired();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid() {
|
||||||
|
return m_ptr.valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
CAtomicSharedPointer<T> lock() const {
|
||||||
|
if (!m_ptr.impl_)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto lg = implLockGuard();
|
||||||
|
|
||||||
|
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return CAtomicSharedPointer<T>(m_ptr.impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
||||||
|
return ((Atomic_::impl<T>*)m_ptr.impl_)->lockGuard();
|
||||||
|
}
|
||||||
|
|
||||||
|
CWeakPointer<T> m_ptr;
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
friend class CAtomicWeakPointer;
|
||||||
|
template <typename U>
|
||||||
|
friend class CAtomicSharedPointer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename U, typename... Args>
|
||||||
|
static CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
|
||||||
|
return CAtomicSharedPointer<U>(new Atomic_::impl<U>(new U(std::forward<Args>(args)...)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
|
||||||
#include <hyprutils/memory/UniquePtr.hpp>
|
#include <hyprutils/memory/UniquePtr.hpp>
|
||||||
|
#include <hyprutils/memory/SharedPtr.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <hyprutils/memory/Atomic.hpp>
|
||||||
#include "shared.hpp"
|
#include "shared.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <print>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace Hyprutils::Memory;
|
using namespace Hyprutils::Memory;
|
||||||
|
|
@ -9,6 +14,87 @@ using namespace Hyprutils::Memory;
|
||||||
#define WP CWeakPointer
|
#define WP CWeakPointer
|
||||||
#define UP CUniquePointer
|
#define UP CUniquePointer
|
||||||
|
|
||||||
|
#define ASP CAtomicSharedPointer
|
||||||
|
#define AWP CAtomicWeakPointer
|
||||||
|
#define NTHREADS 8
|
||||||
|
#define ITERATIONS 10000
|
||||||
|
|
||||||
|
static int testAtomicImpl() {
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Using makeShared here could lead to invalid refcounts.
|
||||||
|
ASP<int> shared = makeAtomicShared<int>(0);
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
|
threads.reserve(NTHREADS);
|
||||||
|
for (size_t i = 0; i < NTHREADS; i++) {
|
||||||
|
threads.emplace_back([shared]() {
|
||||||
|
for (size_t j = 0; j < ITERATIONS; j++) {
|
||||||
|
ASP<int> strongRef = shared;
|
||||||
|
(*shared)++;
|
||||||
|
strongRef.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actual count is not incremented in a thread-safe manner here, so we can't check it.
|
||||||
|
// We just want to check that the concurent refcounting doesn't cause any memory corruption.
|
||||||
|
shared.reset();
|
||||||
|
EXPECT(shared, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASP<int> shared = makeAtomicShared<int>(0);
|
||||||
|
AWP<int> weak = shared;
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
|
threads.reserve(NTHREADS);
|
||||||
|
for (size_t i = 0; i < NTHREADS; i++) {
|
||||||
|
threads.emplace_back([weak]() {
|
||||||
|
for (size_t j = 0; j < ITERATIONS; j++) {
|
||||||
|
if (auto s = weak.lock(); s) {
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
shared.reset();
|
||||||
|
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT(shared.strongRef(), 0);
|
||||||
|
EXPECT(weak.valid(), false);
|
||||||
|
|
||||||
|
auto shared2 = weak.lock();
|
||||||
|
EXPECT(shared, false);
|
||||||
|
EXPECT(shared2.get(), nullptr);
|
||||||
|
EXPECT(shared.strongRef(), 0);
|
||||||
|
EXPECT(weak.valid(), false);
|
||||||
|
EXPECT(weak.expired(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // This tests recursive deletion. When foo will be deleted, bar will be deleted within the foo dtor.
|
||||||
|
class CFoo {
|
||||||
|
public:
|
||||||
|
AWP<CFoo> bar;
|
||||||
|
};
|
||||||
|
|
||||||
|
ASP<CFoo> foo = makeAtomicShared<CFoo>();
|
||||||
|
foo->bar = foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv, char** envp) {
|
int main(int argc, char** argv, char** envp) {
|
||||||
SP<int> intPtr = makeShared<int>(10);
|
SP<int> intPtr = makeShared<int>(10);
|
||||||
SP<int> intPtr2 = makeShared<int>(-1337);
|
SP<int> intPtr2 = makeShared<int>(-1337);
|
||||||
|
|
@ -62,5 +148,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
EXPECT(*intPtr2AsUint, 10);
|
EXPECT(*intPtr2AsUint, 10);
|
||||||
EXPECT(*intPtr2, 10);
|
EXPECT(*intPtr2, 10);
|
||||||
|
|
||||||
|
EXPECT(testAtomicImpl(), 0);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue