Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Excessive memory use in QNetworkAccessManager after network disruption
Forum Updated to NodeBB v4.3 + New Features

Excessive memory use in QNetworkAccessManager after network disruption

Scheduled Pinned Locked Moved Unsolved General and Desktop
11 Posts 2 Posters 822 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N Offline
    N Offline
    nyakacs
    wrote on last edited by
    #1

    Hi, I have a system with an unreliable mobile network connection. I make https posts of about 100K data sporadically using QNetworkAccessManager::post. The QNetworkReply objects are deleted after every post using deleteLater(), so is the QIODevice containing the data as well as the QNetworkAccessManager itself.

    In normal operation the system works as it should and memory usage is very stable. However, sometimes, the post just hangs due to network disruptions. I have a timer to detect this and call QNetworkReply::abort() to abort the post delete all the objects and try in at a later time. I see no excess memory taken when this happens.

    However after an abort was done every subsequent post will start using up more and more memory. The data transmitted will be correct, but the application will eventually eat up all the available memory. The amount of memory lost per post varies.

    A cut down bits of the code are given below. The idea is that I want the post to be blocking so I emit a QueuedConnection signal from the Send() function to the slotSend slot which lives in it's own thread with its own event loop.

    The QNetworkAccessManagers finished() signal is directly connected to the slotResponseReceived() slot when processing followed by clean-up is done.

    When the timeout happens, the timer started when the post was made trigger the slotTimeout(), aborts the post. This triggers the finished() signal form the QNetworkAccessManagers and the clean-up happens. Until this point everything works. The problem is that subsequent posts will start accumulating more and more memory.

    The system is running windows 7 with Qt 5.3.1 and open-ssl 1.0.1q.

    I have no idea on how to skin this one, would appreciate any idea.

    bool Connection::Send(const RelayPayload& payload)
    {
      if(m_wait_on_receipt.available()>0)
        m_wait_on_receipt.acquire(m_wait_on_receipt.available());
    
      QMutexLocker locker(&m_send_mutex);
    
      QByteArray to_send;
    
    //Put some data into the array
    
      QBuffer* buffer_to_send = new QBuffer;
      buffer_to_send->setData(to_send);
      buffer_to_send->open(QIODevice::ReadOnly);
    
      QUrl url;
      //add URL
    
      QMap<QByteArray, QByteArray> header;
      header.insert("Content-Type", "text/xml");
    
      m_is_error=COM_OK;
      emit signalSend(url, buffer_to_send, header);
      //------ Await the completion of the send ------
      m_wait_on_receipt.acquire(1);
    
      if(m_is_error==COM_OK)
        return true; //we got a receipt
    
      return false;
    }
    
    void Connection::slotResponseReceived(QNetworkReply* reply)
    {
      //in the Network manager thread
      m_is_error = ERROR;
    
      if(reply!=nullptr)
      {
        //process response
        m_is_error = COM_OK;
        reply->manager()->deleteLater();
        reply->deleteLater();
      }
    
       m_wait_on_receipt.release(1);
    }
    
    void SendPrivate::slotSend(QUrl url, QIODevice* data, QMap<QByteArray, QByteArray> header)
    {
      QNetworkRequest upload_req(url);
      upload_req.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true);
      upload_req.setHeader(QNetworkRequest::ContentLengthHeader, data->size());
    
      QMapIterator<QByteArray, QByteArray> it(header);
      while (it.hasNext())
      {
        it.next();
        upload_req.setRawHeader(it.key(), it.value());
      }
    
      QNetworkAccessManager *manager = new QNetworkAccessManager();
      connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotResponseReceived(QNetworkReply*)));
    
      m_reply = manager->post(upload_req, data);
      data->setParent(m_reply);
    
      connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(slotError(QNetworkReply::NetworkError)));
      
      m_timeout_timer.start();
    }
    
    void PostPrivate::slotTimeout()
    {
      m_reply->abort();
    }
    
    1 Reply Last reply
    0
    • Christian EhrlicherC Offline
      Christian EhrlicherC Offline
      Christian Ehrlicher
      Lifetime Qt Champion
      wrote on last edited by Christian Ehrlicher
      #2
      1. Don't use threads - QNetworkAccessManager is async and creating threads internally when needed.
      2. Don't create more than one QNetworkAccessManager - RTM: https://doc.qt.io/qt-5/qnetworkaccessmanager.html#details

      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
      Visit the Qt Academy at https://academy.qt.io/catalog

      1 Reply Last reply
      2
      • N Offline
        N Offline
        nyakacs
        wrote on last edited by
        #3

        Hello Christian,

        1. I use a semaphore to block. This also blocks the event loop. I seem to remember that there was a deadlock issue because of this, but I can certainly investigate removing the access to QNetworkAccessManager methods form its own thread. However the actual original call is form already a thread

        2. I don't there is only one at a time. I had a previous version when the QNetworkAccessManager was destroyed so there was only ever one. I have just changed in an attempt to clean up all internal status. It made no difference.

        How these two related to the issue of excessive memory use after network disruption but perfect operation otherwise?
        Is it that methods of QNetworkAccessManager can only be called form the GUI thread and must live in the GUI thread?
        Serialisation is already ensure by a mutex. There is only one call at a time, so is it the thread affinity you think is the problem?

        1 Reply Last reply
        0
        • Christian EhrlicherC Offline
          Christian EhrlicherC Offline
          Christian Ehrlicher
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Again: don't use any sort of threading when trying to get something with QNetworkAccessManager and only create it once and not on every new request again. If you modified your code this way and still getting errors then show us your (compilable) code.

          Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
          Visit the Qt Academy at https://academy.qt.io/catalog

          1 Reply Last reply
          1
          • N Offline
            N Offline
            nyakacs
            wrote on last edited by
            #5

            I did just that. The QNetworkAccessManager now live sin the main application thread. It is created at startup and not destroyed until exit. The trigger for the send comes from a worker thread but I queued connected it so the actaul network bit is all inthe main thread. It changed nothing at all. I will get togherther some compilable code soon.

            1 Reply Last reply
            0
            • N Offline
              N Offline
              nyakacs
              wrote on last edited by
              #6

              And this is the code sanitised:

              #include <QtNetwork/QNetworkReply>
              #include <QtNetwork/QNetworkRequest>
              #include <QtNetwork/qnetworkaccessmanager.h>
              #include <QMutex>
              #include <QSemaphore>
              #include <QTimer>
              #include <QUrl>
              
              #define TIMEOUT  10*1000
              
              class SendClass: public QObject
              {
                  Q_OBJECT
                public:
                  SendClass();
              
                  bool Send(QUrl url, const QByteArray& payload);
              
                signals:
                  void signalSend(QUrl, QByteArray, QMap<QByteArray, QByteArray>);
              
                private slots:
                  void slotResponseReceived(QNetworkReply* reply);
                  void slotSend(QUrl url, QByteArray data, QMap<QByteArray, QByteArray> header);
                  void slotTimeout();
              
                private :
                  QNetworkAccessManager m_networkManager;
                  QTimer m_timeout_timer;
                  QNetworkReply* m_reply;
                  QMutex m_send_mutex;
                  QSemaphore m_wait_on_receipt;
                  bool m_is_error;
              };
              
              SendClass::SendClass()
              {
                qRegisterMetaType< QMap<QByteArray,QByteArray> > ( "QMap<QByteArray,QByteArray>" );
              
                m_timeout_timer.setSingleShot(true);
                m_timeout_timer.setInterval(TIMEOUT);
                connect(&m_timeout_timer,SIGNAL(timeout()), this, SLOT(slotTimeout()));
                connect(&m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotResponseReceived(QNetworkReply*)));
                connect(this, SIGNAL(signalSend(QUrl, QByteArray, QMap<QByteArray, QByteArray>)), this, SLOT(slotSend(QUrl, QByteArray, QMap<QByteArray, QByteArray>)), Qt::QueuedConnection);
              }
              
              bool SendClass::Send(QUrl url, const QByteArray& payload)
              {
                //called from worker thread
              
                QMutexLocker locker(&m_send_mutex);
               
                QMap<QByteArray, QByteArray> header;
                header.insert("Content-Type", "text/xml");
              
                m_is_error=false;
                emit signalSend(url, payload, header);
                //------ Await the completion of the send ------
                m_wait_on_receipt.acquire(1);
              
                return !m_is_error;
              }
              
              void SendClass::slotSend(QUrl url, QByteArray data, QMap<QByteArray, QByteArray> header)
              {
                QNetworkRequest upload_req(url);
                upload_req.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true);
                upload_req.setHeader(QNetworkRequest::ContentLengthHeader, data.size());
              
                QMapIterator<QByteArray, QByteArray> it(header);
                while (it.hasNext())
                {
                  it.next();
                  upload_req.setRawHeader(it.key(), it.value());
                }
              
                m_reply = m_networkManager.post(upload_req, data);
                m_timeout_timer.start();
              }
              
              void SendClass::slotResponseReceived(QNetworkReply* reply)
              {
                  m_is_error = true;
              
                  if(reply!=nullptr)
                  {
                    QVariant statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
                    if ( statusCode.isValid() )
                    {
                      int status = statusCode.toInt();
                      if(status==200)
                      {
                        QByteArray replyData = reply->readAll();
                        //do sothing with result
                        m_is_error = false;
                      }
                    }
              
                    reply->deleteLater();
                  }
                  m_wait_on_receipt.release(1);
              }
              
              void SendClass::slotTimeout()
              {
                m_is_error=true;
                m_reply->abort();
              }
              
              1 Reply Last reply
              0
              • Christian EhrlicherC Offline
                Christian EhrlicherC Offline
                Christian Ehrlicher
                Lifetime Qt Champion
                wrote on last edited by
                #7

                @nyakacs said in Excessive memory use in QNetworkAccessManager after network disruption:

                m_reply = m_networkManager.post(upload_req, data);

                When you send more than one request before the previous is finished (timed out) this will leak.

                Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                Visit the Qt Academy at https://academy.qt.io/catalog

                1 Reply Last reply
                1
                • N Offline
                  N Offline
                  nyakacs
                  wrote on last edited by
                  #8

                  Because of the QMutexLocker at the beginning of Send() no new call can come through until Send() is finished. The m_wait_on_receipt semaphore will not allow Send() to finish before slotResponseReceived() cleans up. If slotResponseReceived() is not called then the whole thing will freeze and wait on the semaphore indefinatelly. This is not the case.

                  The timeout only calls QNetworkReply::abort(). Abort will trigger QNetworkAccessManager to emit the finished() signal which is connected to slotResponseReceived() and cleans up.

                  Unless I really miss something big, how can I have more than one request before the previous finished and where is the leak?

                  1 Reply Last reply
                  0
                  • Christian EhrlicherC Offline
                    Christian EhrlicherC Offline
                    Christian Ehrlicher
                    Lifetime Qt Champion
                    wrote on last edited by
                    #9

                    So you block the main thread until the request is finished? Mhhh
                    Why do you need all this mutex stuff? I don't see a reason. You know when a request is on the way (m_request is != nullptr) anyway so you can react properly.

                    Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                    Visit the Qt Academy at https://academy.qt.io/catalog

                    1 Reply Last reply
                    0
                    • N Offline
                      N Offline
                      nyakacs
                      wrote on last edited by
                      #10

                      It implements a blocking send. The Send() function does not return until we know that the send succeeded or failed. The worker thread needs to behave differetly regarding the success of the send because the data needs to be sent in strict order. The Mutex garantees that the Send() function cannot be reentered until the full network cycle is finished.

                      In the mean time I have increased the network timeout to 20sec. This ment that there is no more timeout but the accumulating memory use is still there. This just proves that the issue is not related to the aborting of the send but what happens to the network many layesr below.

                      I have two identical machines, one is always accunmulating memory, the other never. The difference is the relyabilty of connection. They are the same hardware and software in every detail.

                      1 Reply Last reply
                      0
                      • N Offline
                        N Offline
                        nyakacs
                        wrote on last edited by
                        #11

                        After a lot more testing and comparing between systems with and without the issue, is does not seem to be related to network reliability after all.

                        System sending regularly and constantly are OK systems spending sporadically take up ever more memory. When about a 100K is sent every 10-20 minutes and additional 40Mb is used up daily compared to sending ever minute. This memory in not released if QNetworkAccessManager is destroyed.

                        Memory is taken during QnetworkAccessManager::post() and the amount can be anything between 2-300K to 3MB at a time and is not related to the amount of data sent.

                        Does anyone have experience with cacheing in QNetworkAccessManager? What is cached by default and how to control it?

                        1 Reply Last reply
                        0

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved