Qt Network seems to persistently use memory
-
I have a program that is riddled with calls that pretty much look like this:
// not the actual site I'm pinging, but you get the idea QNetworkRequest req{QUrl{"https://qt.io"}}; req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setRawHeader("X-Api-Key", m_apiKey); // using head here means that there's not as much content to download, thereby speeding up the app auto rep = m_manager.head(req); connect(rep, &QNetworkReply::finished, this, [this, rep]() { if (auto status = rep->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); status == 200) [[likely]] { // well look at that, we are online } else [[unlikely]] { // somebody cut a cable or something, so we'd better throw an error message or something } rep->deleteLater(); }); while (!rep->isFinished()) qApp->processEvents();Other than the fact that this has potential to crash (yes, I understand that calling
rep->deleteLater()in this context has the potential to delete rep before the final call torep->isFinished(); my actual code has a workaround), this has a major problem: it eats memory. Granted, it was worse before I realized thatQNetworkReplydoesn't delete itself, but it still eats memory.My code is actually structured so that the network calling stuff in my above code block is mostly separated into functions that look like this:
// there are also head, post, and patch available; I could trivially add any other HTTP verb void get(const QUrl &url, bool async, int expectedReturnCode, const std::function<void (QNetworkReply *)> &successCb, const std::function<void (QNetworkReply *)> &failureCb);so that I can simply call
head(QUrl{"https://qt.io"}, true, 200, [] { // internet connected, do something! }, [] { // internet not connected, do something! });and have it behave as expected. To access the callbacks, I'm storing them in a
QHash<QNetworkReply *, QPair<std::function<void (QNetworkReply *)>, std::function<void (QNetworkReply *)>>>. I suspected this as possibly being a memory eater but it doesn't seem to actually be the cause; when I tested without using the hash callback method by replacing thehead()call with the actual code fromhead(), memory usage still went up.A typical memory usage scenario looks like this when observed in Heaptrack:

I'm running on openSUSE Tumbleweed using Qt 6.1.2 and/or Qt 5.15.2. I did notice that 5.15.2 had a significantly lower baseline memory usage than 6.1.2; however, both exhibit the same memory-eating behavior.
I understand that (a) this could be an obscure issue and (b) this is a lot of information to parse; however, hopefully I will be able to find a solution. If I left out anything important, please let me know.
Thanks in advance!
-
Hi,
Did you try to run your application through valgrind as well ?
-
Well, I must admit that I haven't run it through valgrind very seriously. (I ran it through once in Qt Creator, IIRC, and quickly decided that Heaptrack was better.)
I should mention that Heaptrack does not mark this excess memory as leaked as far as I can tell.As I was writing this, I noticed that one of the largest memory leaks (inOPENSSL_LH_insert, located in libcrypto.so.1.1) seems to correspond to the amount of "memory creep" I've found. Perhaps it's an OpenSSL bug? -
I am seeing Wayland related stuff there, did you try to run your application as console only ? It's all the more things removed that may have an influence.
-
Do you mind explaining what you mean by "console only"? Does that mean removing all GUI code and testing the application or something else?
-
Do you mind explaining what you mean by "console only"? Does that mean removing all GUI code and testing the application or something else?
-
I have a program that is riddled with calls that pretty much look like this:
// not the actual site I'm pinging, but you get the idea QNetworkRequest req{QUrl{"https://qt.io"}}; req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setRawHeader("X-Api-Key", m_apiKey); // using head here means that there's not as much content to download, thereby speeding up the app auto rep = m_manager.head(req); connect(rep, &QNetworkReply::finished, this, [this, rep]() { if (auto status = rep->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); status == 200) [[likely]] { // well look at that, we are online } else [[unlikely]] { // somebody cut a cable or something, so we'd better throw an error message or something } rep->deleteLater(); }); while (!rep->isFinished()) qApp->processEvents();Other than the fact that this has potential to crash (yes, I understand that calling
rep->deleteLater()in this context has the potential to delete rep before the final call torep->isFinished(); my actual code has a workaround), this has a major problem: it eats memory. Granted, it was worse before I realized thatQNetworkReplydoesn't delete itself, but it still eats memory.My code is actually structured so that the network calling stuff in my above code block is mostly separated into functions that look like this:
// there are also head, post, and patch available; I could trivially add any other HTTP verb void get(const QUrl &url, bool async, int expectedReturnCode, const std::function<void (QNetworkReply *)> &successCb, const std::function<void (QNetworkReply *)> &failureCb);so that I can simply call
head(QUrl{"https://qt.io"}, true, 200, [] { // internet connected, do something! }, [] { // internet not connected, do something! });and have it behave as expected. To access the callbacks, I'm storing them in a
QHash<QNetworkReply *, QPair<std::function<void (QNetworkReply *)>, std::function<void (QNetworkReply *)>>>. I suspected this as possibly being a memory eater but it doesn't seem to actually be the cause; when I tested without using the hash callback method by replacing thehead()call with the actual code fromhead(), memory usage still went up.A typical memory usage scenario looks like this when observed in Heaptrack:

I'm running on openSUSE Tumbleweed using Qt 6.1.2 and/or Qt 5.15.2. I did notice that 5.15.2 had a significantly lower baseline memory usage than 6.1.2; however, both exhibit the same memory-eating behavior.
I understand that (a) this could be an obscure issue and (b) this is a lot of information to parse; however, hopefully I will be able to find a solution. If I left out anything important, please let me know.
Thanks in advance!
@LorenDB said in Qt Network seems to persistently use memory:
I have a program that is riddled with calls that pretty much look like this:
[...] while (!rep->isFinished()) qApp->processEvents();Is there a reason this is avoiding the event loop by busy waiting? DeferredDelete events have some restrictions which this code appears to be tripping over.
#include <QCoreApplication> #include <QObject> #include <QDebug> #include <QTimer> void tryWithExec() { QObject *obj = new QObject; obj->setObjectName("timeout slot"); QObject::connect(obj, &QObject::destroyed, [](QObject *object) { qDebug() << object << "destroyed"; QCoreApplication::quit(); } ); obj->deleteLater(); qDebug() << "QCoreApplication::processEvents()"; QCoreApplication::instance()->processEvents(); qDebug() << "returning to event loop"; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QObject *obj = new QObject; obj->setObjectName("pre-exec"); QObject::connect(obj, &QObject::destroyed, [](QObject *object) { qDebug() << object<< "destroyed";} ); QTimer timer; timer.setInterval(1000); timer.setSingleShot(true); QObject::connect(&timer, &QTimer::timeout, &tryWithExec); timer.start(); obj->deleteLater(); qDebug() << "QCoreApplication::processEvents()"; a.processEvents(); qDebug() << "QCoreApplication::exec()"; return a.exec(); }Using Qt 5.15.2, the output is:
QCoreApplication::processEvents()
QCoreApplication::exec()
QObject(0x7fd68440b590, name = "pre-exec") destroyed
QCoreApplication::processEvents()
returning to event loop
QObject(0x7fd686006a70, name = "timeout slot") destroyedNote that deferred deletion is not happening in response to QCoreApplication::processEvents().
-
Wow, I have all sorts of advice! Let me sort through this:
@SGaist I already tested with a console application that only does the internet check calls (no other network activity is occurring, at least not anything that I wrote into the test app). The memory creep is still there.
@JoeCFD I'd rather not log into X for various reasons (for example, I'm on KDE Plasma, which for some reason has terrible X support for graphical effects anymore). In fact, I don't think that the fault rests with Wayland, since I've experienced memory creep on Windows as well and since my console test app (see above) also experienced memory creep.
@jeremy_k Hmm, that's a good point. In fact, your suggestion triggered a memory that I had of seeing something similar in Qt Assistant. I looked it up and found that you can call
sendPostedEvents()to do such things as executeDeferredDeleteevents. I will try executing that as well and get back with the results (preliminary testing looks promising). In answer to your question, yes, I have a reason for busy waiting: I specifically want to make sure that network requests execute in a specific order (I'm modifying time records on Clockify, if you want to know, and requesting a stop and then a start can sometimes result in requests finishing in the wrong order which leads to wacky behavior) and decided to add synchronous execution to my network requests that needed it. (I do have the ability to run async requests, however.)Thanks to all involved for the input so far.
-
https://forum.qt.io/topic/128729/real-confusion-about-when-to-delete-qnetworkreply-object/10
Check this one out and you may be able to get some help.
In your code,
if auto rep = m_manager.head(req); finishes immediately, the following connect is useless since finished has been sent out. -
@JoeCFD said in Qt Network seems to persistently use memory:
https://forum.qt.io/topic/128729/real-confusion-about-when-to-delete-qnetworkreply-object/10
Check this one out and you may be able to get some help.
In your code,
if auto rep = m_manager.head(req); finishes immediately, the following connect is useless since finished has been sent out.I tried that without success. I then tried calling
setAutoDeleteReplies(true)on myQNetworkAccessManager, also without success.Another thing that I should point out that I am sure that the problem lies at least partly with Qt Network because when conditions are such that my status requests to Clockify return large(ish) amounts of data, memory creep is larger than when there is little to no data returned. Before anybody asks, no, I am not permanently storing all of this data unless the JSON library I'm using is caching things that I'm parsing behind the scenes.
-
-
Wow, I have all sorts of advice! Let me sort through this:
@SGaist I already tested with a console application that only does the internet check calls (no other network activity is occurring, at least not anything that I wrote into the test app). The memory creep is still there.
@JoeCFD I'd rather not log into X for various reasons (for example, I'm on KDE Plasma, which for some reason has terrible X support for graphical effects anymore). In fact, I don't think that the fault rests with Wayland, since I've experienced memory creep on Windows as well and since my console test app (see above) also experienced memory creep.
@jeremy_k Hmm, that's a good point. In fact, your suggestion triggered a memory that I had of seeing something similar in Qt Assistant. I looked it up and found that you can call
sendPostedEvents()to do such things as executeDeferredDeleteevents. I will try executing that as well and get back with the results (preliminary testing looks promising). In answer to your question, yes, I have a reason for busy waiting: I specifically want to make sure that network requests execute in a specific order (I'm modifying time records on Clockify, if you want to know, and requesting a stop and then a start can sometimes result in requests finishing in the wrong order which leads to wacky behavior) and decided to add synchronous execution to my network requests that needed it. (I do have the ability to run async requests, however.)Thanks to all involved for the input so far.
@LorenDB said in Qt Network seems to persistently use memory:
I specifically want to make sure that network requests execute in a specific order
Chain the sending of the next request to the completion of the current reply. Eg:
void Manager::onReplyFinished(QNetworkReply *reply) { if (!this->m_requestQueue.isEmpty()) { this->get(this->m_requestsQueue.takeFirst()); } // process the current reply } -
@LorenDB said in Qt Network seems to persistently use memory:
I specifically want to make sure that network requests execute in a specific order
Chain the sending of the next request to the completion of the current reply. Eg:
void Manager::onReplyFinished(QNetworkReply *reply) { if (!this->m_requestQueue.isEmpty()) { this->get(this->m_requestsQueue.takeFirst()); } // process the current reply }@jeremy_k the problem is that that approach won't work for my whole application. Some requests return data that needs to be processed by the caller (e.g. the caller is asking if a certain condition is met, and that condition is determined by the state of an online resource).
-
@jeremy_k the problem is that that approach won't work for my whole application. Some requests return data that needs to be processed by the caller (e.g. the caller is asking if a certain condition is met, and that condition is determined by the state of an online resource).
@LorenDB said in Qt Network seems to persistently use memory:
@jeremy_k the problem is that that approach won't work for my whole application. Some requests return data that needs to be processed by the caller (e.g. the caller is asking if a certain condition is met, and that condition is determined by the state of an online resource).
So have the caller connect to QNetworkReply::finished(), or use QNetworkRequest::originatingObject() to indicate which object should be notified.
-
@LorenDB said in Qt Network seems to persistently use memory:
@jeremy_k the problem is that that approach won't work for my whole application. Some requests return data that needs to be processed by the caller (e.g. the caller is asking if a certain condition is met, and that condition is determined by the state of an online resource).
So have the caller connect to QNetworkReply::finished(), or use QNetworkRequest::originatingObject() to indicate which object should be notified.
@jeremy_k said in Qt Network seems to persistently use memory:
So have the caller connect to QNetworkReply::finished(), or use QNetworkRequest::originatingObject() to indicate which object should be notified.
That won't really work in my situation. I'm processing data that depends on the value of this reply, and I need to temporarily hang in order to finish. If I try to do a slot-based callback system, my code will quickly turn into an unmanageable jungle (oh wait, it already is... /s).
-
@jeremy_k said in Qt Network seems to persistently use memory:
So have the caller connect to QNetworkReply::finished(), or use QNetworkRequest::originatingObject() to indicate which object should be notified.
That won't really work in my situation. I'm processing data that depends on the value of this reply, and I need to temporarily hang in order to finish. If I try to do a slot-based callback system, my code will quickly turn into an unmanageable jungle (oh wait, it already is... /s).