diff options
author | Chris Adams <christopher.adams@nokia.com> | 2011-08-26 13:54:00 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2011-09-06 08:29:53 +0200 |
commit | 08e829e3a9dae0230678a8471275cebb4c8fb54e (patch) | |
tree | fdbbc6b718ef026cac7c929ca710277478a4739c /src/declarative/qml/v8 | |
parent | 0c0f03e2b9756afb4e868a76734cc90102218f0a (diff) |
Add garbage collector prologue callback to qv8engine
This commit provides a generic way to manage persistent handles
created by QML so that circular references don't cause leaks, by
utilising v8's garbage collector callbacks.
Change-Id: Ia898197fdf5d86b90915b835ce3e532f7d400de4
Reviewed-on: http://codereview.qt.nokia.com/3688
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
Diffstat (limited to 'src/declarative/qml/v8')
-rw-r--r-- | src/declarative/qml/v8/qv8engine.cpp | 139 | ||||
-rw-r--r-- | src/declarative/qml/v8/qv8gccallback_p.h | 112 | ||||
-rw-r--r-- | src/declarative/qml/v8/v8.pri | 1 |
3 files changed, 252 insertions, 0 deletions
diff --git a/src/declarative/qml/v8/qv8engine.cpp b/src/declarative/qml/v8/qv8engine.cpp index 5b74f48170..6f584174c9 100644 --- a/src/declarative/qml/v8/qv8engine.cpp +++ b/src/declarative/qml/v8/qv8engine.cpp @@ -43,6 +43,7 @@ #include "qv8contextwrapper_p.h" #include "qv8valuetypewrapper_p.h" +#include "qv8gccallback_p.h" #include "qv8include_p.h" #include "../../../3rdparty/javascriptcore/DateMath.h" @@ -134,6 +135,7 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership) v8::Context::Scope context_scope(m_context); v8::V8::SetUserObjectComparisonCallbackFunction(ObjectComparisonCallback); + QV8GCCallback::registerGcPrologueCallback(); m_stringWrapper.init(); m_contextWrapper.init(this); @@ -2293,5 +2295,142 @@ void QV8Engine::emitSignalHandlerException() emit q->signalHandlerException(scriptValueFromInternal(uncaughtException())); } +QThreadStorage<QV8GCCallback::ThreadData *> QV8GCCallback::threadData; +void QV8GCCallback::initializeThreadData() +{ + QV8GCCallback::ThreadData *newThreadData = new QV8GCCallback::ThreadData; + threadData.setLocalData(newThreadData); +} + +void QV8GCCallback::registerGcPrologueCallback() +{ + if (!threadData.hasLocalData()) + initializeThreadData(); + + QV8GCCallback::ThreadData *td = threadData.localData(); + if (!td->gcPrologueCallbackRegistered) { + td->gcPrologueCallbackRegistered = true; + v8::V8::AddGCPrologueCallback(QV8GCCallback::garbageCollectorPrologueCallback, v8::kGCTypeMarkSweepCompact); + } +} + +QV8GCCallback::Node::Node(PrologueCallback callback) + : prologueCallback(callback) +{ +} + +QV8GCCallback::Node::~Node() +{ + node.remove(); +} + +QV8GCCallback::Referencer::Referencer() +{ + v8::HandleScope handleScope; + v8::Handle<v8::Context> context = v8::Context::New(); + v8::Context::Scope contextScope(context); + strongReferencer = qPersistentNew(v8::Object::New()); +} + +void QV8GCCallback::Referencer::addRelationship(QObject *object, QObject *other) +{ + bool handleShouldBeStrong = false; + v8::Persistent<v8::Object> *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong); + v8::Persistent<v8::Value> handle = QDeclarativeData::get(other, true)->v8object; + if (handleShouldBeStrong) { + v8::V8::AddImplicitReferences(strongReferencer, &handle, 1); + } else if (!implicitOwner->IsEmpty()) { + v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1); + } +} + +void QV8GCCallback::Referencer::addRelationship(QObject *object, v8::Persistent<v8::Value> handle) +{ + if (handle.IsEmpty()) + return; + + bool handleShouldBeStrong = false; + v8::Persistent<v8::Object> *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong); + if (handleShouldBeStrong) { + v8::V8::AddImplicitReferences(strongReferencer, &handle, 1); + } else if (!implicitOwner->IsEmpty()) { + v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1); + } +} + +v8::Persistent<v8::Object> *QV8GCCallback::Referencer::findOwnerAndStrength(QObject *object, bool *shouldBeStrong) +{ + QObject *parent = object->parent(); + if (!parent) { + // if the object has JS ownership, the object's v8object owns the lifetime of the persistent value. + if (QDeclarativeEngine::objectOwnership(object) == QDeclarativeEngine::JavaScriptOwnership) { + *shouldBeStrong = false; + return &(QDeclarativeData::get(object)->v8object); + } + + // no parent, and has CPP ownership - doesn't have an implicit parent. + *shouldBeStrong = true; + return 0; + } + + // if it is owned by CPP, it's root parent may still be owned by JS. + // in that case, the owner of the persistent handle is the root parent's v8object. + while (parent->parent()) + parent = parent->parent(); + + if (QDeclarativeEngine::objectOwnership(parent) == QDeclarativeEngine::JavaScriptOwnership) { + // root parent is owned by JS. It's v8object owns the persistent value in question. + *shouldBeStrong = false; + return &(QDeclarativeData::get(parent)->v8object); + } else { + // root parent has CPP ownership. The persistent value should not be made weak. + *shouldBeStrong = true; + return 0; + } +} + +/* + Ensure that each persistent handle is strong if it has CPP ownership + and has no implicitly JS owned object owner in its parent chain, and + weak otherwise. + + Any weak handle whose parent object is still alive will have an implicit + reference (between the parent and the handle) added, so that it will + not be collected. + + Note that this callback is registered only for kGCTypeMarkSweepCompact + collection cycles, as it is during collection cycles of that type + in which weak persistent handle callbacks are called when required. + */ +void QV8GCCallback::garbageCollectorPrologueCallback(v8::GCType, v8::GCCallbackFlags) +{ + if (!threadData.hasLocalData()) + return; + + QV8GCCallback::ThreadData *td = threadData.localData(); + QV8GCCallback::Node *currNode = td->gcCallbackNodes.first(); + + while (currNode) { + // The client which adds itself to the list is responsible + // for maintaining the correct implicit references in the + // specified callback. + currNode->prologueCallback(&td->referencer, currNode); + currNode = td->gcCallbackNodes.next(currNode); + } +} + +void QV8GCCallback::addGcCallbackNode(QV8GCCallback::Node *node) +{ + if (!threadData.hasLocalData()) + initializeThreadData(); + + QV8GCCallback::ThreadData *td = threadData.localData(); + td->gcCallbackNodes.insert(node); +} + +QV8GCCallback::ThreadData::~ThreadData() +{ +} + QT_END_NAMESPACE diff --git a/src/declarative/qml/v8/qv8gccallback_p.h b/src/declarative/qml/v8/qv8gccallback_p.h new file mode 100644 index 0000000000..095c0e5bac --- /dev/null +++ b/src/declarative/qml/v8/qv8gccallback_p.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV8GCCALLBACK_P_H +#define QV8GCCALLBACK_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtCore/qthreadstorage.h> +#include <private/qv8_p.h> +#include <private/qintrusivelist_p.h> + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QV8GCCallback +{ +private: + class ThreadData; +public: + static void garbageCollectorPrologueCallback(v8::GCType, v8::GCCallbackFlags); + static void registerGcPrologueCallback(); + + class Referencer { + public: + ~Referencer() {} + void addRelationship(QObject *object, v8::Persistent<v8::Value> handle); + void addRelationship(QObject *object, QObject *other); + private: + Referencer(); + static v8::Persistent<v8::Object> *findOwnerAndStrength(QObject *qobjectOwner, bool *shouldBeStrong); + v8::Persistent<v8::Object> strongReferencer; + friend class QV8GCCallback::ThreadData; + }; + + class Node { + public: + typedef void (*PrologueCallback)(Referencer *r, Node *node); + Node(PrologueCallback callback); + ~Node(); + + QIntrusiveListNode node; + PrologueCallback prologueCallback; + }; + + static void addGcCallbackNode(Node *node); + +private: + class ThreadData { + public: + ThreadData() : gcPrologueCallbackRegistered(false) { } + ~ThreadData(); + Referencer referencer; + bool gcPrologueCallbackRegistered; + QIntrusiveList<Node, &Node::node> gcCallbackNodes; + }; + + static void initializeThreadData(); + static QThreadStorage<ThreadData *> threadData; +}; + +QT_END_NAMESPACE + +#endif // QV8GCCALLBACK_P_H + diff --git a/src/declarative/qml/v8/v8.pri b/src/declarative/qml/v8/v8.pri index 175efd6b6c..4b642179d3 100644 --- a/src/declarative/qml/v8/v8.pri +++ b/src/declarative/qml/v8/v8.pri @@ -8,6 +8,7 @@ HEADERS += \ $$PWD/qv8debug_p.h \ $$PWD/qv8stringwrapper_p.h \ $$PWD/qv8engine_p.h \ + $$PWD/qv8gccallback_p.h \ $$PWD/qv8contextwrapper_p.h \ $$PWD/qv8qobjectwrapper_p.h \ $$PWD/qv8typewrapper_p.h \ |