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. Real confusion about when to delete QNetworkReply object

Real confusion about when to delete QNetworkReply object

Scheduled Pinned Locked Moved Solved General and Desktop
31 Posts 7 Posters 2.6k Views
  • 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.
  • SGaistS SGaist

    You know that deleteLater does not change that pointer to a nullptr value ?

    H Offline
    H Offline
    hbatalha
    wrote on last edited by
    #11

    @SGaist said in Real confusion about when to delete QNetworkReply object:

    You know that deleteLater does not change that pointer to a nullptr value ?

    Yeah, which is making me confused on why it's crashing the app. Any ideas?

    I made some changes in these two functions:

    void FileDownloader::download(QString url, QString dest_file, QString downloadName, FileDownloadRequestType fileDownloadRequestType)
    {
        this->show();
    
        downloadBegan = false;
        finished = false;
    
        connect(ui->pushButton, &QPushButton::clicked, [this] { this->close(); });
    
        if(MAIN.preferences.theme == Preferences::Theme::DARK)
        {
            ui->msg_label->setStyleSheet("color : white;");
            ui->progress_label->setStyleSheet("color : white;");
        }
        else  if(MAIN.preferences.theme == Preferences::Theme::LIGHT)
        {
            ui->msg_label->setStyleSheet("color : black;");
            ui->progress_label->setStyleSheet("color : black;");
        }
    
        ui->download_progressBar->setMinimum(0);
        ui->download_progressBar->setMaximum(0);
    
        ui->msg_label->setText(tr("Downloading ") + downloadName);
    
        reply = net.get(QNetworkRequest(QUrl(url)));
    
        downloadBegan = true;
        finished = false;
    
        if(QFile::exists(dest_file))
            QFile::remove(dest_file);
    
        QObject::connect(reply, &QNetworkReply::downloadProgress, [this](qint64 received, qint64 total)
        {
            qDebug() << received << " of " << total;
    
            ui->progress_label->setText(Util::displaySize(received) + " of " + Util::displaySize(total));
    
            ui->download_progressBar->setRange(0, total);
            ui->download_progressBar->setValue(received);
        });
    
        QObject::connect(&net, &QNetworkAccessManager::finished, [this, dest_file, fileDownloadRequestType](QNetworkReply* reply)
        {
            finished = true;
    
            if(! reply->error())
            {
                QByteArray data = reply->readAll();
                reply->deleteLater();
    
                QFile file(dest_file);
    
                if( ! file.open(QIODevice::ReadWrite | QIODevice::Truncate))
                    Util::displayErrorMessage(tr("Something went wrong"), tr("Could not save the file"), this, true);
    
                qDebug() << "Size written: " <<  file.write(data);
                file.close();
    
                ui->msg_label->setText(tr("Download Complete"));
                ui->progress_label->setText("");
    
                exe = dest_file;
    
                if(fileDownloadRequestType == FileDownloadRequestType::UPDATE)
                    handleUpdateDownloadFinished();
            }
        });
    }
    
    void FileDownloader::closeEvent(QCloseEvent *)
    {
        if( downloadBegan && ! finished)
            reply->abort();
    }
    

    Instead of using the reply object to check if the download has finished I am using bool variables. I call deleteLater in the slot.

    JKSHJ 1 Reply Last reply
    0
    • JoeCFDJ JoeCFD

      Use this to check if reply finishes before it is deleted.
      connect( m_reply, &QNetworkReply::finished, **** );

      Normally, you may do the following because it may finish immediately and the slot may not be called. Sigh!

         if ( nullptr != m_reply ) { /* check reply is null or not first */
             if ( m_reply->isRunning() ) { /* connect when it is still running */
                 connect( m_reply, &QNetworkReply::finished,
                          this,    &getFinished() );
             }
             else { /* not running(already finished) and call slot directly while connection is not needed */
                 getFinished();
             }
         }
      
      H Offline
      H Offline
      hbatalha
      wrote on last edited by
      #12

      @JoeCFD I didn't quite understand the code

      JoeCFDJ 1 Reply Last reply
      0
      • H hbatalha

        @JoeCFD I didn't quite understand the code

        JoeCFDJ Offline
        JoeCFDJ Offline
        JoeCFD
        wrote on last edited by
        #13

        @hbatalha added comments.

        H 1 Reply Last reply
        0
        • fcarneyF Offline
          fcarneyF Offline
          fcarney
          wrote on last edited by fcarney
          #14

          We do something like this:

          connect(reply, &QNetworkReply::finished, someobject, &SomeObject::someHandler); // or lambda
          connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater);
          

          This keeps the lifetime of the reply separate from the "someobject".

          C++ is a perfectly valid school of magic.

          H 1 Reply Last reply
          0
          • H hbatalha

            @SGaist said in Real confusion about when to delete QNetworkReply object:

            You know that deleteLater does not change that pointer to a nullptr value ?

            Yeah, which is making me confused on why it's crashing the app. Any ideas?

            I made some changes in these two functions:

            void FileDownloader::download(QString url, QString dest_file, QString downloadName, FileDownloadRequestType fileDownloadRequestType)
            {
                this->show();
            
                downloadBegan = false;
                finished = false;
            
                connect(ui->pushButton, &QPushButton::clicked, [this] { this->close(); });
            
                if(MAIN.preferences.theme == Preferences::Theme::DARK)
                {
                    ui->msg_label->setStyleSheet("color : white;");
                    ui->progress_label->setStyleSheet("color : white;");
                }
                else  if(MAIN.preferences.theme == Preferences::Theme::LIGHT)
                {
                    ui->msg_label->setStyleSheet("color : black;");
                    ui->progress_label->setStyleSheet("color : black;");
                }
            
                ui->download_progressBar->setMinimum(0);
                ui->download_progressBar->setMaximum(0);
            
                ui->msg_label->setText(tr("Downloading ") + downloadName);
            
                reply = net.get(QNetworkRequest(QUrl(url)));
            
                downloadBegan = true;
                finished = false;
            
                if(QFile::exists(dest_file))
                    QFile::remove(dest_file);
            
                QObject::connect(reply, &QNetworkReply::downloadProgress, [this](qint64 received, qint64 total)
                {
                    qDebug() << received << " of " << total;
            
                    ui->progress_label->setText(Util::displaySize(received) + " of " + Util::displaySize(total));
            
                    ui->download_progressBar->setRange(0, total);
                    ui->download_progressBar->setValue(received);
                });
            
                QObject::connect(&net, &QNetworkAccessManager::finished, [this, dest_file, fileDownloadRequestType](QNetworkReply* reply)
                {
                    finished = true;
            
                    if(! reply->error())
                    {
                        QByteArray data = reply->readAll();
                        reply->deleteLater();
            
                        QFile file(dest_file);
            
                        if( ! file.open(QIODevice::ReadWrite | QIODevice::Truncate))
                            Util::displayErrorMessage(tr("Something went wrong"), tr("Could not save the file"), this, true);
            
                        qDebug() << "Size written: " <<  file.write(data);
                        file.close();
            
                        ui->msg_label->setText(tr("Download Complete"));
                        ui->progress_label->setText("");
            
                        exe = dest_file;
            
                        if(fileDownloadRequestType == FileDownloadRequestType::UPDATE)
                            handleUpdateDownloadFinished();
                    }
                });
            }
            
            void FileDownloader::closeEvent(QCloseEvent *)
            {
                if( downloadBegan && ! finished)
                    reply->abort();
            }
            

            Instead of using the reply object to check if the download has finished I am using bool variables. I call deleteLater in the slot.

            JKSHJ Offline
            JKSHJ Offline
            JKSH
            Moderators
            wrote on last edited by
            #15

            @hbatalha said in Real confusion about when to delete QNetworkReply object:

            @SGaist said in Real confusion about when to delete QNetworkReply object:

            You know that deleteLater does not change that pointer to a nullptr value ?

            Yeah, which is making me confused on why it's crashing the app. Any ideas?

            1. Most important: Set FileDownloader::reply to nullptr after you call deleteLater().
            2. Second most important: Give your member variable a different name. Don't call it reply, since your lamba's parameter is also called reply. This causes shadowing (https://www.learncpp.com/cpp-tutorial/variable-shadowing-name-hiding/ )

            Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

            1 Reply Last reply
            3
            • jeremy_kJ Offline
              jeremy_kJ Offline
              jeremy_k
              wrote on last edited by
              #16

              For Qt 5.14 and later, theres QNetworkAccessmanager::setAutoDeleteReplies() which calls deleteLater() after emitting the finished signal.

              Asking a question about code? http://eel.is/iso-c++/testcase/

              H 1 Reply Last reply
              2
              • JoeCFDJ JoeCFD

                @hbatalha added comments.

                H Offline
                H Offline
                hbatalha
                wrote on last edited by
                #17

                @JoeCFD Thanks I understand now.
                I didn't know that it could finish immediately and the slot may not be called. What would happen if that happens. I am curious.

                /* check reply is null or not first */

                Why would we do something like that. Is there any chance that the QNetworkAccessManager::get() may return a nullptr?

                JoeCFDJ 1 Reply Last reply
                0
                • fcarneyF fcarney

                  We do something like this:

                  connect(reply, &QNetworkReply::finished, someobject, &SomeObject::someHandler); // or lambda
                  connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater);
                  

                  This keeps the lifetime of the reply separate from the "someobject".

                  H Offline
                  H Offline
                  hbatalha
                  wrote on last edited by
                  #18

                  @fcarney said in Real confusion about when to delete QNetworkReply object:

                  connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater);

                  So this is the same as @jeremy_k answer: QNetworkAccessmanager::setAutoDeleteReplies() ?

                  1 Reply Last reply
                  0
                  • jeremy_kJ jeremy_k

                    For Qt 5.14 and later, theres QNetworkAccessmanager::setAutoDeleteReplies() which calls deleteLater() after emitting the finished signal.

                    H Offline
                    H Offline
                    hbatalha
                    wrote on last edited by
                    #19

                    @jeremy_k So that will eradicate the need to call deleteLater?

                    JKSHJ 1 Reply Last reply
                    0
                    • H hbatalha

                      @jeremy_k So that will eradicate the need to call deleteLater?

                      JKSHJ Offline
                      JKSHJ Offline
                      JKSH
                      Moderators
                      wrote on last edited by JKSH
                      #20

                      @hbatalha said in Real confusion about when to delete QNetworkReply object:

                      So that will eradicate the need to call deleteLater?

                      Yes, that means you don't need to call deleteLater() yourself. But you must still set your pointer to nullptr.

                      OR, avoid storing the QNetworkReply as a member variable.

                      Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                      H 1 Reply Last reply
                      0
                      • JoeCFDJ JoeCFD

                        Use this to check if reply finishes before it is deleted.
                        connect( m_reply, &QNetworkReply::finished, **** );

                        Normally, you may do the following because it may finish immediately and the slot may not be called. Sigh!

                           if ( nullptr != m_reply ) { /* check reply is null or not first */
                               if ( m_reply->isRunning() ) { /* connect when it is still running */
                                   connect( m_reply, &QNetworkReply::finished,
                                            this,    &getFinished() );
                               }
                               else { /* not running(already finished) and call slot directly while connection is not needed */
                                   getFinished();
                               }
                           }
                        
                        jeremy_kJ Offline
                        jeremy_kJ Offline
                        jeremy_k
                        wrote on last edited by jeremy_k
                        #21

                        @JoeCFD said in Real confusion about when to delete QNetworkReply object:

                        Normally, you may do the following because it may finish immediately and the slot may not be called. Sigh!

                        Can you provide a citation? QNetworkAccessManager creates its own worker thread(s), and seems to use queued connections for communication with the worker objects that carry out the requests. I also don't see evidence of this extra check in the HTTP example

                        A demonstration that works for me (but proves nothing):

                        #include <QCoreApplication>
                        #include <QNetworkAccessManager>
                        #include <QNetworkReply>
                        #include <QUrl>
                        #include <QNetworkRequest>
                        #include <QDebug>
                        #include <QThread>
                        
                        int main(int argc, char *argv[])
                        {
                            QCoreApplication a(argc, argv);
                            QNetworkAccessManager manager;
                            QUrl url("http://forum.qt.io");
                            QNetworkRequest request(url);
                            qDebug() << "Fetching" << url;
                            QNetworkReply *reply = manager.get(request);
                            QThread::sleep(60); // Wait a minute!
                            QObject::connect(reply, &QNetworkReply::finished, []() {
                                qDebug() << "request finished";
                                QCoreApplication::instance()->quit();
                            });
                            return a.exec();
                        }
                        
                        

                        Asking a question about code? http://eel.is/iso-c++/testcase/

                        JoeCFDJ 1 Reply Last reply
                        0
                        • JKSHJ JKSH

                          @hbatalha said in Real confusion about when to delete QNetworkReply object:

                          So that will eradicate the need to call deleteLater?

                          Yes, that means you don't need to call deleteLater() yourself. But you must still set your pointer to nullptr.

                          OR, avoid storing the QNetworkReply as a member variable.

                          H Offline
                          H Offline
                          hbatalha
                          wrote on last edited by
                          #22

                          @JKSH

                          Yes, that means you don't need to call deleteLater() yourself. But you must still set your pointer to nullptr.

                          I only need to call it once in the constructor?

                          OR, avoid storing the QNetworkReply as a member variable.

                          The only reason I have it as a member variable is because I need a way to call abort() to cancel a download.

                          JKSHJ 1 Reply Last reply
                          0
                          • H hbatalha

                            @JoeCFD Thanks I understand now.
                            I didn't know that it could finish immediately and the slot may not be called. What would happen if that happens. I am curious.

                            /* check reply is null or not first */

                            Why would we do something like that. Is there any chance that the QNetworkAccessManager::get() may return a nullptr?

                            JoeCFDJ Offline
                            JoeCFDJ Offline
                            JoeCFD
                            wrote on last edited by JoeCFD
                            #23

                            @hbatalha said in Real confusion about when to delete QNetworkReply object:

                            @JoeCFD Thanks I understand now.
                            I didn't know that it could finish immediately and the slot may not be called. What would happen if that happens. I am curious.

                            Read my code: if it finishes, you call the slot directly.

                            /* check reply is null or not first */

                            Why would we do something like that. Is there any chance that the QNetworkAccessManager::get() may return a nullptr?
                            Pure habit.

                            1 Reply Last reply
                            0
                            • jeremy_kJ jeremy_k

                              @JoeCFD said in Real confusion about when to delete QNetworkReply object:

                              Normally, you may do the following because it may finish immediately and the slot may not be called. Sigh!

                              Can you provide a citation? QNetworkAccessManager creates its own worker thread(s), and seems to use queued connections for communication with the worker objects that carry out the requests. I also don't see evidence of this extra check in the HTTP example

                              A demonstration that works for me (but proves nothing):

                              #include <QCoreApplication>
                              #include <QNetworkAccessManager>
                              #include <QNetworkReply>
                              #include <QUrl>
                              #include <QNetworkRequest>
                              #include <QDebug>
                              #include <QThread>
                              
                              int main(int argc, char *argv[])
                              {
                                  QCoreApplication a(argc, argv);
                                  QNetworkAccessManager manager;
                                  QUrl url("http://forum.qt.io");
                                  QNetworkRequest request(url);
                                  qDebug() << "Fetching" << url;
                                  QNetworkReply *reply = manager.get(request);
                                  QThread::sleep(60); // Wait a minute!
                                  QObject::connect(reply, &QNetworkReply::finished, []() {
                                      qDebug() << "request finished";
                                      QCoreApplication::instance()->quit();
                                  });
                                  return a.exec();
                              }
                              
                              
                              JoeCFDJ Offline
                              JoeCFDJ Offline
                              JoeCFD
                              wrote on last edited by JoeCFD
                              #24

                              @jeremy_k examples often show how things work. However, it may not include all you may need. I use it in posting and experienced issues.

                              1 Reply Last reply
                              0
                              • H hbatalha

                                @JKSH

                                Yes, that means you don't need to call deleteLater() yourself. But you must still set your pointer to nullptr.

                                I only need to call it once in the constructor?

                                OR, avoid storing the QNetworkReply as a member variable.

                                The only reason I have it as a member variable is because I need a way to call abort() to cancel a download.

                                JKSHJ Offline
                                JKSHJ Offline
                                JKSH
                                Moderators
                                wrote on last edited by
                                #25

                                @hbatalha said in Real confusion about when to delete QNetworkReply object:

                                @JKSH

                                Yes, that means you don't need to call deleteLater() yourself. But you must still set your pointer to nullptr.

                                I only need to call it once in the constructor?

                                You must set your pointer to nullptr every time the object is deleted.

                                Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

                                H 1 Reply Last reply
                                0
                                • JKSHJ JKSH

                                  @hbatalha said in Real confusion about when to delete QNetworkReply object:

                                  @JKSH

                                  Yes, that means you don't need to call deleteLater() yourself. But you must still set your pointer to nullptr.

                                  I only need to call it once in the constructor?

                                  You must set your pointer to nullptr every time the object is deleted.

                                  H Offline
                                  H Offline
                                  hbatalha
                                  wrote on last edited by
                                  #26

                                  @JKSH This is how I did it

                                  QObject::connect(&net, &QNetworkAccessManager::finished, [this, dest_file, url, fileDownloadRequestType](QNetworkReply* reply)
                                      {
                                          finished = true;
                                  
                                          if(! reply->error())
                                          {
                                              QByteArray data = reply->readAll();
                                  
                                              QFile file(dest_file);
                                  
                                              if( ! file.open(QIODevice::ReadWrite | QIODevice::Truncate))
                                                  Util::displayErrorMessage(tr("Something went wrong"), tr("Could not save the file"), this, true);
                                  
                                              file.write(data);
                                              file.close();
                                  
                                              ui->msg_label->setText(tr("Download Complete"));
                                              ui->progress_label->setText("");
                                  
                                              exe = dest_file;
                                  
                                              if(fileDownloadRequestType == FileDownloadRequestType::UPDATE)
                                                  handleUpdateDownloadFinished();
                                          }
                                          else if(reply->errorString() != "Operation canceled")
                                          {
                                              LOG_ERROR("Error Downloading from " + url + "\nDescription: " +  reply->errorString());
                                              reply = nullptr;
                                              Util::displayErrorMessage(tr("Network Error"), tr("Please try again later"), this, true);
                                          }
                                  
                                          m_reply = nullptr;
                                      });
                                  
                                  jsulmJ 1 Reply Last reply
                                  0
                                  • H hbatalha

                                    @JKSH This is how I did it

                                    QObject::connect(&net, &QNetworkAccessManager::finished, [this, dest_file, url, fileDownloadRequestType](QNetworkReply* reply)
                                        {
                                            finished = true;
                                    
                                            if(! reply->error())
                                            {
                                                QByteArray data = reply->readAll();
                                    
                                                QFile file(dest_file);
                                    
                                                if( ! file.open(QIODevice::ReadWrite | QIODevice::Truncate))
                                                    Util::displayErrorMessage(tr("Something went wrong"), tr("Could not save the file"), this, true);
                                    
                                                file.write(data);
                                                file.close();
                                    
                                                ui->msg_label->setText(tr("Download Complete"));
                                                ui->progress_label->setText("");
                                    
                                                exe = dest_file;
                                    
                                                if(fileDownloadRequestType == FileDownloadRequestType::UPDATE)
                                                    handleUpdateDownloadFinished();
                                            }
                                            else if(reply->errorString() != "Operation canceled")
                                            {
                                                LOG_ERROR("Error Downloading from " + url + "\nDescription: " +  reply->errorString());
                                                reply = nullptr;
                                                Util::displayErrorMessage(tr("Network Error"), tr("Please try again later"), this, true);
                                            }
                                    
                                            m_reply = nullptr;
                                        });
                                    
                                    jsulmJ Offline
                                    jsulmJ Offline
                                    jsulm
                                    Lifetime Qt Champion
                                    wrote on last edited by
                                    #27

                                    @hbatalha But now you don't call deleteLater() on reply, right?
                                    You need to call deleteLater() AND set the pointer to nullptr afterwards.

                                    https://forum.qt.io/topic/113070/qt-code-of-conduct

                                    H 1 Reply Last reply
                                    0
                                    • jsulmJ jsulm

                                      @hbatalha But now you don't call deleteLater() on reply, right?
                                      You need to call deleteLater() AND set the pointer to nullptr afterwards.

                                      H Offline
                                      H Offline
                                      hbatalha
                                      wrote on last edited by
                                      #28

                                      @jsulm said in Real confusion about when to delete QNetworkReply object:

                                      @hbatalha But now you don't call deleteLater() on reply, right?
                                      You need to call deleteLater() AND set the pointer to nullptr afterwards.

                                      I didn't call it because I called QNetworkAccessmanager::setAutoDeleteReplies() in the constructor.

                                      H 1 Reply Last reply
                                      0
                                      • H hbatalha

                                        @jsulm said in Real confusion about when to delete QNetworkReply object:

                                        @hbatalha But now you don't call deleteLater() on reply, right?
                                        You need to call deleteLater() AND set the pointer to nullptr afterwards.

                                        I didn't call it because I called QNetworkAccessmanager::setAutoDeleteReplies() in the constructor.

                                        H Offline
                                        H Offline
                                        hbatalha
                                        wrote on last edited by
                                        #29

                                        Update:
                                        So after running some tests and downloading multiple files I noticed a strange thing:
                                        The slot connected to &QNetworkAccessManager::finished gets called more than once if I give it some time and keeps getting called. And because I set the reply to null in the first call, in the second call it overwrites the just downloaded file and writes null, making the FileDownloader pretty much useless.

                                        After some debugging I found out that because FileDownloader::download everyt ime is called with the same object in the MainWindow it the QNetworkAccessManager object gets connected more than once and thus calling the slot more than once.

                                        I have a few options to solve this:

                                        • Check if ( nullptr != m_reply ) before connecting like @JoeCFD suggested in his answer
                                        • Disconnecting the QNetworkAccessManager object and connecting it again
                                        • Do not use lambda and create a method for that and connect it once in the constructor ( this is the option IMO but because I use some variables that come from the FileDownloader::download parameters inside the slot, it makes it difficult to use this option unless I created the member variables for those parameters.
                                        1 Reply Last reply
                                        0
                                        • SGaistS Offline
                                          SGaistS Offline
                                          SGaist
                                          Lifetime Qt Champion
                                          wrote on last edited by
                                          #30

                                          If you have some stuff that is associated to the reply, you can use dynamic properties to keep everything in one place or use a QMap to store the reply specific data that you will then grab in your lambda.

                                          Interested in AI ? www.idiap.ch
                                          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                                          H 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