QML context object with JavaScriptOwnership garbage collected
-
The following program demonstrates the issue I'm having:
class App : public QObject { Q_OBJECT public: virtual ~App() = default; Q_INVOKABLE bool f() const { return false; } }; int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlEngine engine; QObject* ctx = new App(); QQmlEngine::setObjectOwnership(ctx, QQmlEngine::JavaScriptOwnership); engine.rootContext()->setContextObject(ctx); QQmlComponent component(&engine); QObject::connect(&component, &QQmlComponent::statusChanged, [&](auto status) { if(status == QQmlComponent::Ready) component.create(); if(status == QQmlComponent::Error) { for(auto err : component.errors()) qCritical() << "Error: " << err.toString(); } }); component.loadUrl(QUrl::fromLocalFile("main.qml")); return app.exec(); }
With the QML document:
import QtQuick 2.0 import QtQuick.Window 2.2 Window { Timer { interval: 1; running: true; repeat: true onTriggered: { console.log("call f"); f(); gc(); console.log("done"); } } }
If I run this, it outputs:
$ ./main qml: call f qml: done qml: call f file:///data/libs/hsqml/member-undefined/main.qml:10: ReferenceError: f is not defined qml: call f fish: './main' terminated by signal SIGSEGV (Adressbereichsfehler)
Is this expected behavior? When is it possible to use QQmlEngine::JavaScriptOwnership safely?
I've already posted this on StackOverflow where it was suggested that I also create a post here.
Since this example is from code in a library, I cannot just use
setContextProperty
(which appears to work), because that would break the library's interface. -
I'm not an expert on the QML runtime, but it seems like a Really Bad Idea (TM) to destroy a context object prior to the destruction of the context. If you need to create and destroy that object during the lifetime of the QML context, a much more natural implementation would be to have a C++ function create and return it, and call that function from javascript to obtain a handle on the object. Is there any reason you need it to be a context object?
Furthermore, I would not advise calling
gc()
explicitly; if you're concerned about the exact lifetime of your objects, I recommend destroying them manually, not calling the garbage collector. Look at http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership for details on QObject ownership between JS and C++. -
@Nathan-H I agree that destroying context objects prior to the destruction of the context is a Bad Idea, that's why I'm surprised that the Qt engine is doing this (there is no explict call in my code to destroy the object)! As for not calling gc() explictly, I agree, I only used gc() in the example to make the behavior of destroying the context object early happen more deterministically and showcase the problem better. In real code, the problem will occur much less frequent, non-deterministically. Because the GC may be invoked at any time, any program should not refuse to work when gc statements are inserted and this is a good test to make sure that the lifetimes of my objects are correct (which in this case, they clearly are not).
-
-
Correct, for this particular case I could perhaps just use CppOwnership, but there are other places in the library where I definitely want JavaScriptOwnership (it's a binding to QML for the Haskell programming language, and you sometimes need "weak pointers" to objects that have been passed to QML). So I'd still be very interested in guidelines / specifics on what exactly I can expect from objects with JavaScriptOwnership.
-
I suspect that there is no special handling for context objects, such that one with JavaScript ownership would be kept alive by the engine specifically.
Unless I have misunderstood, in the test case you have posted, you are doing:
- expose context object (which has JS ownership)
- at time t0 resolve it within a JS context -- at this point the engine starts tracking it via references
- at time t1 drop the previous reference, and manually call gc -- at this point the engine will delete it
This is expected behaviour. The object which you specifically declared to have JavaScript ownership, had its JS reference count drop from 1 to 0 and then the garbage collector was invoked.
If you had set CppOwnership on it, then you would be saying "this object should be kept alive even after all JS references to it have been destroyed" - but you explicitly asked the engine to destroy it once all JS references had been destroyed, by setting JavaScriptOwnership.
Or have I misunderstood your test case?
-
I know this topic is old, but I'm wondering if any of the participants here made any progress with integrating the QML garbage collector with an external garbage collector (such as .NET)?
Here is a SO question that will give some context: https://stackoverflow.com/questions/52471189/qt-using-setcontextproperty-with-qobject-and-javascriptownership-causes-i