Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QML Memory leak sending XMLHttpRequest

Creating new instances of XMLHttpRequest and calling send() results in memory usage that can't be cleared by the garbage collector, nor gc(). Calling delete on the object doesnt' clear the memory neither.

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480

    Component.onCompleted: {
        for(var i = 0; i < 100000; i++) {
            console.log("Send request " + i)

            var xhttp = new XMLHttpRequest
            xhttp.open('get', 'someurl')
            xhttp.send()
            delete xhttp
        }

        gc() //why won't this clean the instances of XMLHttpRequest???
    }
}

If I never call xhttp.send() then I don't have any memory leak. Garbage collection kicks in since there's no reference to the var xhttp and the memory is freed. I thought maybe the garbage collector wasn't triggering, but gc() wouldn't clear the memory neither.

This MRE will run 100,000 iterations and holds about 500MB in memory. This can easily hold 5.0GB by changing to i < 1000000.

How do I fix this memory leak and free the memory?

References to similar questions: QTBUG-43005 (No resolution) QTBUG-50231 (No resolution)

Now documented on QTBUG-83857

enter image description here Here it is, holding 2.0GB of memory. It held it for nearly 4 hours until I killed the program. When I closed the application, after about 60 seconds the whole 2GB memory was freed

An Attempt at using QNetworkAccessManager class

//main.qml
    MyClass {
        id: myNetworkClass

        Component.onCompleted: {
            for(var i = 0; i < 10000; i++) {
                myNetworkClass.doDownload()
            }
        }
    }
//myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>


class MyClass : public QObject
{
    Q_OBJECT

public:
    explicit MyClass(QObject *parent = 0):QObject(){
        manager = new QNetworkAccessManager(this);

        connect(manager, SIGNAL(finished(QNetworkReply*)),
                this, SLOT(replyFinished(QNetworkReply*)));
    }

public:
    Q_INVOKABLE void doDownload() {
        manager->get(QNetworkRequest(QUrl("https://esi.evetech.net/latest/characters/93610700")));
    }

public slots:
    void replyFinished(QNetworkReply *reply) {};

private:
    QNetworkAccessManager *manager;
    int count = 0;
};

#endif // MYCLASS_H

This unfortunately also holds onto memory and doesn't release it, according to htop.

like image 364
Tyler M Avatar asked Apr 23 '20 02:04

Tyler M


Video Answer


1 Answers

EDIT

Create a new controlling class in C++ to handle your web requests and then set a root context property or register the type in QML and use the C++ api instead.

Here's the simplest way to go:

in myclass.cpp you create a method with this type of code along with the appropriate handlers (replyFinished)

Make sure the method you want to call from QML is prefixed with Q_INVOKABLE in the header file (right before void)

QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished,
        this, &MyClass::replyFinished);

manager->get(QNetworkRequest(QUrl("http://qt-project.org")));

--

Now you simply register the type and create an instance in main.cpp

 qmlRegisterType<MyClass>("MyClass", 1,0, "MyClass");

--

and in your QML file do

import MyClass 1.0
Window {
   MyClass { 
     id: myNetworkClass
   }

  function doLookup() { myNetworkClass.myCustomMethod(); }
}

This will give you a stable way to avoid the QML issues that stem from running a native javascript environment which is asynchronous and weakly typed on a strongly typed framework...

Good luck!

ORIGINAL

First off, you are leaving hanging object references by creating the request without saving a reference to the created object...

Keep an array of all XMLHttpRequest objects in a property of Window

Window
{
    property var requests: []
// ...

    Timer {
       onTriggered: { 
 //  add request to array
             requests.push(xhttp);
       }
    }
}

Then maybe start deleting the objects in your array with ...

var xhttp = requests.unshift() 
xhttp.destroy()

But the real issue is in sending http requests at 50ms intervals

Thats ummm 20 per second or 1200/minute requests

You might want to adjust the code to send a request once the previous one finishes.. or set the interval to a higher value

like image 106
mike510a Avatar answered Oct 16 '22 10:10

mike510a