Correct code to prevent memory leak with QNAM HTTP Get request to nonexistent URL with Qt 5.14.2
-
What is the correct way to handle HTTP Get requests to a nonexistent URL in the case of the remote server going down?
I'm testing the edge case of the remote server going down.I am seeing what I think is a memory leak and I am unsure of what I am supposed to be doing to clean up the request in the event of a timeout occurring.
I am using Syrupy to periodically capture the output of ps to see memory usage.Note - I am aware of Qt 5.15 including a new API call (setTransferTimeout) to set a timeout length but I cannot update on this project yet.
Testing on Ubuntu 20.04 with Linux kernel 4.19 as well as a Raspberry Pi 4 (Buster) with Linux kernel 5.4.79
Syrupy log output from an 1.5 hour run:
PID DATE TIME ELAPSED CPU MEM RSS VSIZE CMD 4814 2020-12-15 17:09:53 00:00 0.0 0.1 4756 36456 ./memoryprofiletest 4814 2020-12-15 17:10:53 01:00 0.5 0.1 5024 55028 ./memoryprofiletest 4814 2020-12-15 17:11:53 02:00 0.5 0.1 5620 55156 ./memoryprofiletest 4814 2020-12-15 17:12:54 03:00 0.5 0.2 8316 55156 ./memoryprofiletest 4814 2020-12-15 17:13:54 04:00 0.5 0.2 8316 55156 ./memoryprofiletest 4814 2020-12-15 17:14:54 05:01 0.5 0.2 8316 55156 ./memoryprofiletest ... 4814 2020-12-15 17:23:55 14:02 0.5 0.2 8512 55412 ./memoryprofiletest 4814 2020-12-15 17:24:55 15:02 0.5 0.2 8512 55412 ./memoryprofiletest 4814 2020-12-15 17:25:55 16:02 0.5 0.2 8512 55412 ./memoryprofiletest 4814 2020-12-15 17:26:55 17:02 0.5 0.2 8512 55412 ./memoryprofiletest ... 4814 2020-12-15 17:37:57 28:04 0.5 0.2 8932 55668 ./memoryprofiletest 4814 2020-12-15 17:38:57 29:04 0.5 0.2 8932 55668 ./memoryprofiletest 4814 2020-12-15 17:39:57 30:04 0.5 0.2 8984 55796 ./memoryprofiletest 4814 2020-12-15 17:40:57 31:04 0.5 0.2 8984 55796 ./memoryprofiletest 4814 2020-12-15 17:41:57 32:04 0.5 0.2 8984 55796 ./memoryprofiletest ... 4814 2020-12-15 17:52:59 43:05 0.5 0.2 9132 56052 ./memoryprofiletest 4814 2020-12-15 17:53:59 44:06 0.5 0.2 9132 56052 ./memoryprofiletest 4814 2020-12-15 17:54:59 45:06 0.5 0.2 9132 56052 ./memoryprofiletest 4814 2020-12-15 17:55:59 46:06 0.5 0.2 9192 56180 ./memoryprofiletest 4814 2020-12-15 17:56:59 47:06 0.5 0.2 9192 56180 ./memoryprofiletest ... 4814 2020-12-15 18:08:01 58:07 0.5 0.2 9516 56436 ./memoryprofiletest 4814 2020-12-15 18:09:01 59:07 0.5 0.2 9516 56436 ./memoryprofiletest 4814 2020-12-15 18:10:01 01:00:08 0.5 0.2 9516 56436 ./memoryprofiletest 4814 2020-12-15 18:11:01 01:01:08 0.5 0.2 9516 56436 ./memoryprofiletest ... 4814 2020-12-15 18:23:03 01:13:09 0.5 0.2 9728 56692 ./memoryprofiletest 4814 2020-12-15 18:24:03 01:14:09 0.5 0.2 10056 56820 ./memoryprofiletest 4814 2020-12-15 18:25:03 01:15:10 0.5 0.2 10056 56820 ./memoryprofiletest 4814 2020-12-15 18:26:03 01:16:10 0.5 0.2 10056 56820 ./memoryprofiletest ... 4814 2020-12-15 18:38:05 01:28:11 0.5 0.2 10196 57076 ./memoryprofiletest 4814 2020-12-15 18:39:05 01:29:11 0.5 0.2 10196 57076 ./memoryprofiletest 4814 2020-12-15 18:40:05 01:30:12 0.5 0.2 10196 57076 ./memoryprofiletest
I've created the simplest possible test case below which is showing the memory increasing above:
// // main.cpp // #include <QCoreApplication> #include "memoryprofiletest.h" int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); MemoryProfileTest memoryProfileTest; return a.exec(); } // ============================================================= // // memoryprofiletest.h // #ifndef MEMORYPROFILETEST_H #define MEMORYPROFILETEST_H #include <QObject> #include <QtCore> #include <QtNetwork> class MemoryProfileTest : public QObject { Q_OBJECT public: explicit MemoryProfileTest(QObject* parent = nullptr); private: void executeNetworkRequest(); QTimer instanceTimer; QNetworkAccessManager networkAccessManager; private slots: void executeRequest(); }; #endif // MEMORYPROFILETEST_H // ============================================================= // // memoryprofiletest.cpp // #include "memoryprofiletest.h" MemoryProfileTest::MemoryProfileTest(QObject *parent) : QObject(parent), networkAccessManager(this) { qDebug() << "Compiled with Qt Version " << QT_VERSION_STR; qDebug() << "Running with Qt Version " << qVersion(); instanceTimer.callOnTimeout(this, &MemoryProfileTest::executeRequest); instanceTimer.start(500); // 500 milliseconds } void MemoryProfileTest::executeNetworkRequest() { auto endpoint = QUrl("http://localhost/api/1.0/aService/aNonexistentEndpoint"); QNetworkRequest request(endpoint); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); auto reply = networkAccessManager.get(request); connect(reply, &QNetworkReply::finished, reply, [reply]() { if (reply->error() != QNetworkReply::NoError) { qDebug() << "finished - error:" << reply->errorString(); } else { auto replyData = reply->readAll(); // Do something useful with the data } reply->deleteLater(); qDebug() << "finished - deleteLater called"; }); }; void MemoryProfileTest::executeRequest() { executeNetworkRequest(); }
-
Hi and welcome to devnet,
You should also handle the errorOccured signal.
I might be wrong but since the URL does not exists then the processing will not have been started and thus finished has no reason to be emitted.
-
Thank you for the welcome and the reply!
The errorOccured signal is not available until Qt 5.15 so I have added the error signal that's available in Qt 5.14.I am running another test now to capture memory usage from a long running test.
Here's the current program output:
Compiled with Qt Version 5.14.2 Running with Qt Version 5.14.2 error lambda: "Connection refused" error lambda - deleteLater called finished lambda - error: "Connection refused" finished lambda - deleteLater called error lambda: "Connection refused" error lambda - deleteLater called finished lambda - error: "Connection refused" finished lambda - deleteLater called ... continues...
Updated test code below:
// // main.cpp // #include <QCoreApplication> #include "memoryprofiletest.h" int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); MemoryProfileTest memoryProfileTest; return a.exec(); } // ============================================================= // // memoryprofiletest.h // #ifndef MEMORYPROFILETEST_H #define MEMORYPROFILETEST_H #include <QObject> #include <QtCore> #include <QtNetwork> class MemoryProfileTest : public QObject { Q_OBJECT public: explicit MemoryProfileTest(QObject* parent = nullptr); private: void executeNetworkRequest(); QTimer instanceTimer; QNetworkAccessManager networkAccessManager; private slots: void executeRequest(); }; #endif // MEMORYPROFILETEST_H // ============================================================= // // memoryprofiletest.cpp // #include "memoryprofiletest.h" MemoryProfileTest::MemoryProfileTest(QObject *parent) : QObject(parent), networkAccessManager(this) { qDebug() << "Compiled with Qt Version " << QT_VERSION_STR; qDebug() << "Running with Qt Version " << qVersion(); instanceTimer.callOnTimeout(this, &MemoryProfileTest::executeRequest); instanceTimer.start(500); // 500 milliseconds } void MemoryProfileTest::executeNetworkRequest() { auto endpoint = QUrl("http://localhost/api/1.0/aService/aNonexistentEndpoint"); QNetworkRequest request(endpoint); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); auto reply = networkAccessManager.get(request); connect(reply, &QNetworkReply::finished, reply, [reply]() { if (reply->error() != QNetworkReply::NoError) { qDebug() << "finished lambda - error:" << reply->errorString(); } else { auto replyData = reply->readAll(); // Do something useful with the data } reply->deleteLater(); qDebug() << "finished lambda - deleteLater called"; }); connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), [reply] (QNetworkReply::NetworkError code){ if (reply->error() != QNetworkReply::NoError) { qDebug() << "error lambda:" << reply->errorString(); } reply->deleteLater(); qDebug() << "error lambda - deleteLater called"; }); }; void MemoryProfileTest::executeRequest() { executeNetworkRequest(); }
-
-
Thank you Christian!
https://bugreports.qt.io/browse/QTBUG-88063 describes exactly what I've been seeing.