gecko-dev/mfbt/ThreadSafeWeakPtr.h
Nika Layzell 18f7866fc2 Bug 1738106 - Part 2: Allow XPCOM classes to use SupportsThreadSafeWeakPtr, r=glandium,xpcom-reviewers
Due to how this type of threadsafe refcounting is implemented, it needs to be
implemented using a MFBT-style refcounting base class, somewhat similar to
`SupportsWeakPtr`.

This patch makes NS_IMPL_{ADDREF,RELEASE}_INHERITED intelligently not add
refcount logging for types inheriting from SupportsThreadSafeWeakPtr, as the
default behaviour would break due to weak pointer upgrades not calling through
`AddRef` or `Release`.

In MFBT, the return value of `AddRef` and `Release` on
SupportsThreadSafeWeakPtr is changed to be compatible with nsISupports, so that
this type can be used to implement nsISupports refcounting.

Differential Revision: https://phabricator.services.mozilla.com/D142603
2022-05-02 20:37:34 +00:00

308 lines
10 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* A thread-safe weak pointer */
/**
* Derive from SupportsThreadSafeWeakPtr to allow thread-safe weak pointers to
* an atomically refcounted derived class. These thread-safe weak pointers may
* be safely accessed and converted to strong pointers on multiple threads.
*
* Note that SupportsThreadSafeWeakPtr defines the same member functions as
* AtomicRefCounted, so you should not separately inherit from it.
*
* ThreadSafeWeakPtr and its implementation is distinct from the normal WeakPtr
* which is not thread-safe. The interface discipline and implementation details
* are different enough that these two implementations are separated for now for
* efficiency reasons. If you don't actually need to use weak pointers on
* multiple threads, you can just use WeakPtr instead.
*
* When deriving from SupportsThreadSafeWeakPtr, you should add
* MOZ_DECLARE_REFCOUNTED_TYPENAME(ClassName) to the public section of your
* class, where ClassName is the name of your class.
*
* Example usage:
*
* class C : public SupportsThreadSafeWeakPtr<C>
* {
* public:
* MOZ_DECLARE_REFCOUNTED_TYPENAME(C)
* void doStuff();
* };
*
* ThreadSafeWeakPtr<C> weak;
* {
* RefPtr<C> strong = new C;
* if (strong) {
* strong->doStuff();
* }
* // Make a new weak reference to the object from the strong reference.
* weak = strong;
* }
* MOZ_ASSERT(!bool(weak), "Weak pointers are cleared after all "
* "strong references are released.");
*
* // Convert the weak reference to a strong reference for usage.
* RefPtr<C> other(weak);
* if (other) {
* other->doStuff();
* }
*/
#ifndef mozilla_ThreadSafeWeakPtr_h
#define mozilla_ThreadSafeWeakPtr_h
#include "mozilla/Assertions.h"
#include "mozilla/RefCountType.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RefPtr.h"
namespace mozilla {
template <typename T>
class ThreadSafeWeakPtr;
template <typename T>
class SupportsThreadSafeWeakPtr;
namespace detail {
class SupportsThreadSafeWeakPtrBase {};
// A shared weak reference that is used to track a SupportsThreadSafeWeakPtr
// object. This object owns the reference count for the tracked object, and can
// perform atomic refcount upgrades.
class ThreadSafeWeakReference
: public external::AtomicRefCounted<ThreadSafeWeakReference> {
public:
explicit ThreadSafeWeakReference(SupportsThreadSafeWeakPtrBase* aPtr)
: mPtr(aPtr) {}
#ifdef MOZ_REFCOUNTED_LEAK_CHECKING
const char* typeName() const { return "ThreadSafeWeakReference"; }
size_t typeSize() const { return sizeof(*this); }
#endif
private:
template <typename U>
friend class mozilla::SupportsThreadSafeWeakPtr;
template <typename U>
friend class mozilla::ThreadSafeWeakPtr;
// Number of strong references to the underlying data structure.
//
// Other than the initial strong `AddRef` call incrementing this value to 1,
// which must occur before any weak references are taken, once this value
// reaches `0` again it cannot be changed.
RC<MozRefCountType, AtomicRefCount> mStrongCnt{0};
// Raw pointer to the tracked object. It is never valid to read this value
// outside of `ThreadSafeWeakPtr::getRefPtr()`.
SupportsThreadSafeWeakPtrBase* MOZ_NON_OWNING_REF mPtr;
};
} // namespace detail
// For usage documentation for SupportsThreadSafeWeakPtr, see the header-level
// documentation.
//
// To understand the layout of SupportsThreadSafeWeakPtr, consider the following
// simplified declaration:
//
// class MyType: SupportsThreadSafeWeakPtr { uint32_t mMyData; ... }
//
// Which will result in the following layout:
//
// +--------------------+
// | MyType | <===============================================+
// +--------------------+ I
// | RefPtr mWeakRef o======> +-------------------------------------+ I
// | uint32_t mMyData | | ThreadSafeWeakReference | I
// +--------------------+ +-------------------------------------+ I
// | RC mRefCount | I
// | RC mStrongCount | I
// | SupportsThreadSafeWeakPtrBase* mPtr o====+
// +-------------------------------------+
//
// The mRefCount inherited from AtomicRefCounted<ThreadSafeWeakReference> is the
// weak count. This means MyType implicitly holds a weak reference, so if the
// weak count ever hits 0, we know all strong *and* weak references are gone,
// and it's safe to free the ThreadSafeWeakReference. MyType's AddRef and
// Release implementations otherwise only manipulate mStrongCount.
//
// It's necessary to keep the counts in a separate allocation because we need
// to be able to delete MyType while weak references still exist. This ensures
// that weak references can still access all the state necessary to check if
// they can be upgraded (mStrongCount).
template <typename T>
class SupportsThreadSafeWeakPtr : public detail::SupportsThreadSafeWeakPtrBase {
protected:
using ThreadSafeWeakReference = detail::ThreadSafeWeakReference;
// The `this` pointer will not have subclasses initialized yet, but it will
// also not be read until a weak pointer is upgraded, which should be after
// this point.
SupportsThreadSafeWeakPtr() : mWeakRef(new ThreadSafeWeakReference(this)) {
static_assert(std::is_base_of_v<SupportsThreadSafeWeakPtr, T>,
"T must derive from SupportsThreadSafeWeakPtr");
}
public:
// Compatibility with RefPtr
MozExternalRefCountType AddRef() const {
auto& refCnt = mWeakRef->mStrongCnt;
MOZ_ASSERT(int32_t(refCnt) >= 0);
MozRefCountType cnt = ++refCnt;
detail::RefCountLogger::logAddRef(static_cast<const T*>(this), cnt);
return cnt;
}
MozExternalRefCountType Release() const {
auto& refCnt = mWeakRef->mStrongCnt;
MOZ_ASSERT(int32_t(refCnt) > 0);
detail::RefCountLogger::ReleaseLogger logger(static_cast<const T*>(this));
MozRefCountType cnt = --refCnt;
logger.logRelease(cnt);
if (0 == cnt) {
// Because we have atomically decremented the refcount above, only one
// thread can get a 0 count here. Thus, it is safe to access and destroy
// |this| here.
// No other thread can acquire a strong reference to |this| anymore
// through our weak pointer, as upgrading a weak pointer always uses
// |IncrementIfNonzero|, meaning the refcount can't leave a zero reference
// state.
// NOTE: We can't update our refcount to the marker `DEAD` value here, as
// it may still be read by mWeakRef.
delete static_cast<const T*>(this);
}
return cnt;
}
// Compatibility with wtf::RefPtr
void ref() { AddRef(); }
void deref() { Release(); }
MozRefCountType refCount() const { return mWeakRef->mStrongCnt; }
bool hasOneRef() const { return refCount() == 1; }
private:
template <typename U>
friend class ThreadSafeWeakPtr;
ThreadSafeWeakReference* getThreadSafeWeakReference() const {
return mWeakRef;
}
const RefPtr<ThreadSafeWeakReference> mWeakRef;
};
// A thread-safe variant of a weak pointer
template <typename T>
class ThreadSafeWeakPtr {
using ThreadSafeWeakReference = detail::ThreadSafeWeakReference;
public:
ThreadSafeWeakPtr() = default;
ThreadSafeWeakPtr& operator=(const ThreadSafeWeakPtr& aOther) = default;
ThreadSafeWeakPtr(const ThreadSafeWeakPtr& aOther) = default;
ThreadSafeWeakPtr& operator=(ThreadSafeWeakPtr&& aOther) = default;
ThreadSafeWeakPtr(ThreadSafeWeakPtr&& aOther) = default;
ThreadSafeWeakPtr& operator=(const RefPtr<T>& aOther) {
if (aOther) {
// Get the underlying shared weak reference to the object.
mRef = aOther->getThreadSafeWeakReference();
} else {
mRef = nullptr;
}
return *this;
}
explicit ThreadSafeWeakPtr(const RefPtr<T>& aOther) { *this = aOther; }
ThreadSafeWeakPtr& operator=(decltype(nullptr)) {
mRef = nullptr;
return *this;
}
explicit ThreadSafeWeakPtr(decltype(nullptr)) {}
// Use the explicit `IsNull()` or `IsDead()` methods instead.
explicit operator bool() const = delete;
// Check if the ThreadSafeWeakPtr was created wrapping a null pointer.
bool IsNull() const { return !mRef; }
// Check if the managed object is nullptr or has already been destroyed. Once
// IsDead returns true, this ThreadSafeWeakPtr can never be upgraded again
// (until it has been re-assigned), but a false return value does NOT imply
// that any future upgrade will be successful.
bool IsDead() const { return IsNull() || size_t(mRef->mStrongCnt) == 0; }
bool operator==(const ThreadSafeWeakPtr& aOther) const {
return mRef == aOther.mRef;
}
bool operator==(const RefPtr<T>& aOther) const {
return *this == aOther.get();
}
friend bool operator==(const RefPtr<T>& aStrong,
const ThreadSafeWeakPtr& aWeak) {
return aWeak == aStrong.get();
}
bool operator==(const T* aOther) const {
if (!mRef) {
return !aOther;
}
return aOther && aOther->getThreadSafeWeakReference() == mRef;
}
template <typename U>
bool operator!=(const U& aOther) const {
return !(*this == aOther);
}
// Convert the weak pointer to a strong RefPtr.
explicit operator RefPtr<T>() const { return getRefPtr(); }
private:
// Gets a new strong reference of the proper type T to the tracked object.
already_AddRefed<T> getRefPtr() const {
if (!mRef) {
return nullptr;
}
// Increment our strong reference count only if it is nonzero, meaning that
// the object is still alive.
MozRefCountType cnt = mRef->mStrongCnt.IncrementIfNonzero();
if (cnt == 0) {
return nullptr;
}
RefPtr<T> ptr = already_AddRefed<T>(static_cast<T*>(mRef->mPtr));
detail::RefCountLogger::logAddRef(ptr.get(), cnt);
return ptr.forget();
}
// A shared weak reference to an object. Note that this may be null so as to
// save memory (at the slight cost of an extra null check) if no object is
// being tracked.
RefPtr<ThreadSafeWeakReference> mRef;
};
} // namespace mozilla
template <typename T>
inline already_AddRefed<T> do_AddRef(
const mozilla::ThreadSafeWeakPtr<T>& aObj) {
RefPtr<T> ref(aObj);
return ref.forget();
}
#endif /* mozilla_ThreadSafeWeakPtr_h */