Delete a dynamically-loaded QQuickItem
-
Hi,
I have an application that creates QQuickItems from QML files and adds them as visual children of a general item.
The pseudo-code is as follows:
QQmlComponent * pQMLComponent = new QQmlComponent( engine, QUrl::fromLocalFile("mycomponent.qml") );
QObject * pObject = pQMLComponent->create ();
pQuickItem = qobject_cast<QQuickItem*>( pObject );
pQuickItem->setParentItem ( pQuickParent ); // Sets the visual parent item
...At some point, I would like to dispose of this component as I no longer need it. I tried calling:
delete pQuickItem;
pQuickItem = NULL;But this crashes quite often on the first line.
I then tried calling:
pQuickItem->setParent( NULL );
pQuickItem->setParentItem( NULL );
pQuickItem->deleteLater();This does not crash but it does not seem to have any effect. I see that the component gets hidden, but my tests show that its destroyed() signal is never called, and the memory is never reclaimed, while the control returns to the event loop and I wait several minutes.
This is a problem for me as I regularly create items and thus memory is leaking.
How can I properly delete such a component?
-
@jrlafff I am pretty sure you are inadvertently given QML ownership of your object. see http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership for what I am talking about.
EDIT: nope I read to fast.
EDIT2: I don't know without code you could be dealing with the issue above (because of the crash). -
Here is the code, simplified:
for ( int i = 0; i < 10000; i++ ) { QQmlComponent * pQMLComponent = new QQmlComponent( engine, QUrl::fromLocalFile("mycomponent.qml") ); QObject * pObject = pQMLComponent->create (); QQuickItem * pQuickItem = qobject_cast<QQuickItem*>( pObject ); //pQuickItem->setParentItem ( pQuickParent ); // Sets the visual parent item - issue persists when no parent is set delete pQMLComponent; pQuickItem->setParent( NULL ); pQuickItem->setParentItem( NULL ); pQuickItem->deleteLater(); }
I made a loop here only to show that 25MB of memory is consumed. This memory is never released.
The content of the mycomponent.qml file is:
import QtQuick 2.2 Item { }
-
@jrlafff Oh ok so there is a difference between your QQuickItem::setParentItem and QObject::setParent. QQuickItem::setParentItem() sets the visual parent where QObject::setParent sets the parent ownership. Parent ownership dictates that when a QObject is deleted it deletes its children. Now granted I'm not possitive why deleteLater doesn't seem to work, but your object doesn't have a parent to delete it nor are you just calling delete on that pointer. So instead try doing
for ( int i = 0; i < 10000; i++ ) { QQmlComponent * pQMLComponent = new QQmlComponent( engine, QUrl::fromLocalFile("mycomponent.qml") ); QObject * pObject = pQMLComponent->create (); QQuickItem * pQuickItem = qobject_cast<QQuickItem*>( pObject ); pQuickItem->setParent(pQuickParent); // Set owning parent pQuickItem->setParentItem ( pQuickParent ); // Sets the visual parent item - issue persists when no parent is set delete pQMLComponent; // you really should consider using a smart pointer }
Now if pQuickParent ever gets deleted (by like being unloaded from a loader) it will be freed automatically for you.
-
It works indeed.
However, the components do not appear the second time I try to create them. More precisely:
- Load.source = "source.qml";
- Create the component and set its parent (and visual parent) to the component loaded (source.qml)
- Load.source = "";
- Create the component and set its parent (and visual parent) to the component loaded (source.qml)
I notice that, after step 3, the component previously loaded can still be found using findChild()... Which makes me think that the components are not destroyed.
-
After nearly two days of investigation, I think I found a workaround.
I have a simple test program that reproduces what I think is a memory leak.
The following C++ code invokes a simple QML method several times:
for ( int i = 0; i < 100; i++ ) QMetaObject::invokeMethod( pRoot, "testMethod" );
The QML method in question is:
function testMethod() { idLoader.source = "Component.qml"; idLoader.source = ""; }
The Component.qml file simply contains a few items.
This causes the memory to increase, each time the code is executed.
By simply changing the ConnectionType to Qt::QueuedConnection (Qt::AutoConnection is the default and the caller lives in the same thread so it uses a DirectConnection by default), the behavior is totally different: the memory increases at the first call, and then stays steady, no matter how many times I run the code below:
for ( int i = 0; i < 100; i++ ) QMetaObject::invokeMethod( pRoot, "testMethod", Qt::QueuedConnection );
This looks like a bug to me, or a race condition causing the JavaScript garbage collector to miss items...
So it seems that:
- Loading components dynamically from the C++ code leaks out;
- Loading them in QML but calling the methods using a DirectConnection leaks out as well.
Anyway, I will try to re-implement the loading algorithm in my application to use QML-side loading with QueuedConnection, and check whether the memory leak is indeed fixed.
-
@jrlafff Good find indeed. I would suggest you to ask the above question on the Qt Interest Mailing List. Most often Qt Engineers answer them.