blob: 46d89819b6a982728bb5737057baec97e629d348 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWebEngine module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef LOCKED_PTR_H
#define LOCKED_PTR_H
#include <base/bind_internal.h>
#include <QtCore/qreadwritelock.h>
namespace base {
struct LockedPtrCore
{
LockedPtrCore(uintptr_t data) : data(data) {}
std::atomic<size_t> refCount{1};
// Atomic so that WeakLockedPtr::get can still read it.
std::atomic<uintptr_t> data;
QReadWriteLock lock{QReadWriteLock::Recursive};
};
enum class LockedPtrMode { Weak, Shared, Exclusive };
template<class T, LockedPtrMode mode> class LockedPtr;
// A WeakLockedPtr<T> is something like shared_ptr<T*>. The T* value can only be
// accessed by atomic read.
template<class T> using WeakLockedPtr = LockedPtr<T, LockedPtrMode::Weak>;
// A SharedLockedPtr<T> is like WeakLockedPtr<T>, but the T* value is prevented
// from changing for the lifetime of the SharedLockedPtr by holding a
// shared-exclusive mutex in shared mode.
template<class T> using SharedLockedPtr = LockedPtr<T, LockedPtrMode::Shared>;
// An ExclusiveLockedPtr<T> is like SharedLockedPtr<T>, but the mutex is held in
// exclusive mode. Only in this mode can the T* value be changed.
template<class T> using ExclusiveLockedPtr = LockedPtr<T, LockedPtrMode::Exclusive>;
template<class T, LockedPtrMode mode>
class LockedPtr
{
template<class T1>
static constexpr bool canConstructFrom =
std::is_same<T, T1>::value ||
std::is_same<T, const T1>::value;
public:
constexpr LockedPtr() {}
constexpr LockedPtr(std::nullptr_t) {}
LockedPtr(const LockedPtr &that)
{
m_core = that.m_core;
lock();
}
LockedPtr &operator=(const LockedPtr &that)
{
unlock();
m_core = that.m_core;
lock();
}
LockedPtr(LockedPtr &&that)
{
m_core = that.m_core;
that.m_core = nullptr;
}
LockedPtr &operator=(LockedPtr &&that)
{
unlock();
m_core = that.m_core;
that.m_core = nullptr;
}
template<class T1, LockedPtrMode mode1,
class Enable = std::enable_if_t<canConstructFrom<T1>>>
LockedPtr(const LockedPtr<T1, mode1> &that)
{
m_core = that.m_core;
lock();
}
template<class T1, LockedPtrMode mode1,
class Enable = std::enable_if_t<canConstructFrom<T1>>>
LockedPtr &operator=(const LockedPtr<T1, mode1> &that)
{
unlock();
m_core = that.m_core;
lock();
}
template<class T1,
class Enable = std::enable_if_t<canConstructFrom<T1>>>
LockedPtr(LockedPtr<T1, mode> &&that)
{
m_core = that.m_core;
that.m_core = nullptr;
}
template<class T1,
class Enable = std::enable_if_t<canConstructFrom<T1>>>
LockedPtr &operator=(LockedPtr<T1, mode> &&that)
{
unlock();
m_core = that.m_core;
that.m_core = nullptr;
}
~LockedPtr()
{
unlock();
}
T *get() const
{
if (m_core) {
if (mode == LockedPtrMode::Weak)
return reinterpret_cast<T *>(m_core->data.load(std::memory_order_acquire));
else
return reinterpret_cast<T *>(m_core->data.load(std::memory_order_relaxed));
}
return nullptr;
}
void set(T *value)
{
static_assert(mode == LockedPtrMode::Exclusive, "");
DCHECK(m_core);
m_core->data.store(reinterpret_cast<uintptr_t>(value), std::memory_order_release);
}
T &operator*() const { return *get(); }
T *operator->() const { return get(); }
explicit operator bool() const { return get(); }
bool MaybeValid() const { return m_core; }
static LockedPtr create(T *value)
{
return new LockedPtrCore(reinterpret_cast<uintptr_t>(value));
}
private:
template<class T1, LockedPtrMode mode1> friend class LockedPtr;
LockedPtr(LockedPtrCore *core)
: m_core(core)
{}
void lock()
{
if (m_core) {
++m_core->refCount;
if (mode == LockedPtrMode::Shared)
m_core->lock.lockForRead();
else if (mode == LockedPtrMode::Exclusive)
m_core->lock.lockForWrite();
}
}
void unlock()
{
if (m_core) {
if (mode != LockedPtrMode::Weak)
m_core->lock.unlock();
if (--m_core->refCount == 0)
delete m_core;
}
}
LockedPtrCore *m_core = nullptr;
};
// This makes Bind check the pointer before calling the functor.
template<class T>
struct IsWeakReceiver<WeakLockedPtr<T>> : std::true_type {};
// By converting the WeakLockedPtr into a SharedLockedPtr we prevent the
// pointed-to object from being destroyed during the base::Callback::Run call.
//
// Unwrap() is called before checking the pointer, so there's no race condition.
template<class T>
struct BindUnwrapTraits<WeakLockedPtr<T>>
{
static SharedLockedPtr<T> Unwrap(const WeakLockedPtr<T> &o)
{
return o;
}
};
// Like base::WeakPtrFactory, but InvalidateWeakPtrs *waits* until all currently
// executing base::Callbacks are finished. Queued up base::Callbacks are still
// canceled, exactly like with WeakPtrFactory.
//
// Consider, for example, the function
//
// void fun()
// {
// MyClass *myClass = new MyClass;
// myClass->scheduleDoStuff();
// delete myClass; // ???
// }
//
// where
//
// class MyClass
// {
// public:
// void scheduleDoStuff()
// {
// content::BrowserThread::PostTask(
// content::BrowserThread::IO, FROM_HERE,
// base::BindOnce(&MyClass::doStuff, m_weakPtrFactory.GetWeakPtr()));
// }
// void doStuff();
// private:
// //base::WeakPtrFactory m_weakPtrFactory{this};
// base::LockedPtrFactory m_weakPtrFactory{this};
// };
//
// What happens if the 'delete myClass' line is executed concurrently with
// MyClass::doStuff?
//
// With WeakPtrs we get a segfault or perhaps memory corruption.
//
// With LockedPtrs we get no crash and no corruption: LockedPtrFactory's
// destructor will wait until doStuff is done before continuing.
template<class T>
class LockedPtrFactory
{
public:
explicit LockedPtrFactory(T *value)
: m_ptr(WeakLockedPtr<T>::create(value))
{}
~LockedPtrFactory()
{
InvalidateWeakPtrs();
}
WeakLockedPtr<T> GetWeakPtr() { return m_ptr; }
WeakLockedPtr<const T> GetWeakPtr() const { return m_ptr; }
SharedLockedPtr<T> GetSharedPtr() { return m_ptr; }
SharedLockedPtr<const T> GetSharedPtr() const { return m_ptr; }
ExclusiveLockedPtr<T> GetExclusivePtr() { return m_ptr; }
ExclusiveLockedPtr<const T> GetExclusivePtr() const { return m_ptr; }
void InvalidateWeakPtrs()
{
if (ExclusiveLockedPtr<T> ptr = m_ptr)
ptr.set(nullptr);
}
private:
WeakLockedPtr<T> m_ptr;
};
} // namespace base
#endif // !LOCKED_PTR_H