memory: fix CAtomicSharedPointer mutex unlock and lifetime issues

This commit is contained in:
Maximilian Seidler 2025-07-21 16:34:48 +02:00
parent bcabcbada9
commit 11bad76c40

View file

@ -23,6 +23,27 @@
namespace Hyprutils::Memory {
namespace Atomic_ {
// Lock guard with the option to unlock early
class CCLockGuard {
std::recursive_mutex* m_mutex;
public:
CCLockGuard(std::recursive_mutex* mutex) : m_mutex(mutex) {
m_mutex->lock();
}
~CCLockGuard() {
unlockDrop();
};
void unlockDrop() {
if (m_mutex)
m_mutex->unlock();
m_mutex = nullptr;
}
};
template <typename T>
class impl : public Impl_::impl<T> {
std::recursive_mutex m_mutex;
@ -32,8 +53,8 @@ namespace Hyprutils::Memory {
;
}
std::lock_guard<std::recursive_mutex> lockGuard() {
return std::lock_guard<std::recursive_mutex>(m_mutex);
CCLockGuard lockGuard() {
return CCLockGuard(&m_mutex);
}
};
}
@ -87,19 +108,12 @@ namespace Hyprutils::Memory {
}
~CAtomicSharedPointer() {
if (!m_ptr.impl_)
return;
auto lg = implLockGuard();
m_ptr.reset();
reset();
}
template <typename U>
validHierarchy<const CAtomicSharedPointer<U>&> operator=(const CAtomicSharedPointer<U>& rhs) {
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
reset();
if (!rhs.m_ptr.impl_)
return *this;
@ -113,10 +127,7 @@ namespace Hyprutils::Memory {
if (this == &rhs)
return *this;
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
reset();
if (!rhs.m_ptr.impl_)
return *this;
@ -145,7 +156,32 @@ namespace Hyprutils::Memory {
return;
auto lg = implLockGuard();
if (m_ptr.impl_->ref() > 1) {
m_ptr.reset();
return;
}
if (m_ptr.impl_->wref() == 0) {
lg.unlockDrop(); // Don't hold the mutex when destroying it
m_ptr.reset();
return;
}
// When the control block gets destroyed, the mutex is destroyed with it.
// Thus we must avoid attempting an unlock after impl_ has been destroyed.
// There is no safe way of checking whether it has been destroyed or not.
//
// To avoid this altogether, keep a weak pointer here.
// This guarantees that impl_ is still valid after the reset.
CWeakPointer<T> guard = m_ptr;
m_ptr.reset();
// Now we can safely check if guard is the last wref.
if (guard.impl_->wref() == 1)
lg.unlockDrop();
// guard.reset()
}
T& operator*() const {
@ -177,7 +213,7 @@ namespace Hyprutils::Memory {
}
private:
std::lock_guard<std::recursive_mutex> implLockGuard() const {
Atomic_::CCLockGuard implLockGuard() const {
return ((Atomic_::impl<T>*)m_ptr.impl_)->lockGuard();
}
@ -241,19 +277,12 @@ namespace Hyprutils::Memory {
}
~CAtomicWeakPointer() {
if (!m_ptr.impl_)
return;
auto lg = implLockGuard();
m_ptr.reset();
reset();
}
template <typename U>
validHierarchy<const CAtomicWeakPointer<U>&> operator=(const CAtomicWeakPointer<U>& rhs) {
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
reset();
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
@ -264,10 +293,7 @@ namespace Hyprutils::Memory {
if (this == &rhs)
return *this;
if (m_ptr.impl_) {
auto lg = implLockGuard();
m_ptr.reset();
}
reset();
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
@ -293,6 +319,9 @@ namespace Hyprutils::Memory {
return;
auto lg = implLockGuard();
if (m_ptr.impl_->ref() == 0 && m_ptr.impl_->wref() == 1)
lg.unlockDrop();
m_ptr.reset();
}
@ -345,7 +374,7 @@ namespace Hyprutils::Memory {
}
private:
std::lock_guard<std::recursive_mutex> implLockGuard() const {
Atomic_::CCLockGuard implLockGuard() const {
return ((Atomic_::impl<T>*)m_ptr.impl_)->lockGuard();
}