GC crashes QML-Application

2019-09-11 12:48发布

Do not fear! This is not production code. It is just to learn new things about QML! I do not seek 'you shall not do something like this - do it like that.' I am more interested in the internals of QML

Consider the following QML-Code

import QtQuick 2.4
import QtQuick.Window 2.0

Window {
    id: root
    width: 800
    height: 600
    visible: true
    GridView {
        width: 800
        height: 200
        model: 4000
        flow: GridView.FlowTopToBottom

        delegate: Rectangle {
            id: myDelegate
            width: 100
            height: 100
            border.width: 1
            Column {
                anchors.fill: parent
                Text {
                    text: index
                    height: 20
                    width: parent.width
                }
                Item {
                    id: frame0
                    height: 20
                    width: parent.width
                }
                Item {
                    id: frame1
                    height: 20
                    width: parent.width
                }
            }

            Component.onCompleted: {
                // if (index % 100 === 0) gc()
                frame0.children = [myComp.createObject(myDelegate)]
                frame1.children = [myComp.createObject(null)]
                frame0.children[0].text = 'QML ' + index

                frame1.children[0].text = 'JS ' + index
            }
        }

        Component {
            id: myComp
            Text {
                anchors.centerIn: parent
                Component.onDestruction: console.log('Destroy ' + text)
            }
        }
    }
}

It illustrates to some extent the MemoryManagement of QML when using dynamic ObjectCreation (JS). I have a ListView, that creates a few delegates, and lets me browse through them, creating new one on demand.

The trick is: When ever a new delegate is created, it uses the JavaScript Dynamic Object Creation to create two instances of an text-object.

One of them is parented to the delegate, the other one is parented to null and it's life is therefore determined by the JS-Engine. It should be garbage collected, once there is no pointer left, pointing towards it.

For the start, I will put both of them in a frame (Item), to display (setting a visual parent). As the delegate, those frames are destroyed. This will - as expected - destroy the dynamically created Object that has the delegate as parent, as well. The other one is (should be) left for the Garbage Collector to do his work.

And this is where it fails - sometimes the application crashes before the GC kicks in, sometimes it crashes, while the GC is trying to do its work.

Though it is not reccommended by the documentation, it does help, to call the GC manualy (activate the line commented out in Component.onCompleted).

So it seems to me, the GC overestimates it's abilities, and decides to kick in, when it is already to late.

What might be the reason for this? Is there a way to tell the GC to be mor proactive?

Again: I do not intend to use the Dynamic Object Creation with .createObject(null) in my code. It is pure curiosity.

1条回答
聊天终结者
2楼-- · 2019-09-11 13:15

What might be the reason for this? Is there a way to tell the GC to be mor proactive?

The reason for this is the buggy qtquick object lifetime implementation. At this point it doesn't look to be a JS thing, but a qtquick thing. It clearly doesn't abide to its own alleged rules - for example, it will delete an object with a parent while still in use, resulting in a hard crash. You cannot expect reference counting to work as well. This behavior can occur in a number of scenarios that employ dynamism, it generally does not manifest in trivial and static scenarios.

The solution, as already outlined in the linked question, is to use manual object lifetime management. Use a set of new functions for the creation and deletion of objects.

  • for creation, you must pass the object to the C++ side in order to call QQmlEngine::setObjectOwnership(ojb, QQmlEngine::CppOwnership); on it, this basically tells qtquick "don't even bother trying", luckily at least this works as it is supposed
  • for destruction, you must pass the object to the C++ side in order to call obj->deleteLater();

For me this does the trick, I no longer get crashes for no apparent reason. Use the custom lifetime management and stay away from the stock functions for that. It gives you guarantees, that the object will stay alive as long as you need it, but also that it will not stay past the point where you want it gone, which is another problem, albeit not that severe. Of course, this eliminates the convenience factor of using JS, as you have to give up on automatic lifetime management and be a little more diligent and explicit with your own code, but there isn't much you can do about it. Even though the bug was reported almost a year ago and deemed critical, not a shred of work has been done about it whatsoever. Thus I assume that as critical as it may be in its severity, it is more like lowest priority when it comes to finding its cause and fixing it.

查看更多
登录 后发表回答