aboutsummaryrefslogtreecommitdiffstats
path: root/src/declarative/qml/v8
diff options
context:
space:
mode:
authorChris Adams <christopher.adams@nokia.com>2011-08-26 13:54:00 +1000
committerQt by Nokia <qt-info@nokia.com>2011-09-06 08:29:53 +0200
commit08e829e3a9dae0230678a8471275cebb4c8fb54e (patch)
treefdbbc6b718ef026cac7c929ca710277478a4739c /src/declarative/qml/v8
parent0c0f03e2b9756afb4e868a76734cc90102218f0a (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.cpp139
-rw-r--r--src/declarative/qml/v8/qv8gccallback_p.h112
-rw-r--r--src/declarative/qml/v8/v8.pri1
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 \