0

****UPDATED: I noticed that I get the segfault only on Windows, on Linux it's fine. On Windows I use QT 5.5 and MinGW32. I still want to know why.

**** Initial Question: Nothing tricky here, I create a basic Console Application. I have a QNetworkAccessManager sending a Post() request. When I close the console, there is a segfault.

Note that the request is sent and received successfully, my question is only about that segfault.

If no Post() request are sent, no crash on closing the console. There is not much help from the stack.

Stack

0   ntdll!RtlFreeHeap           0x77b5e041  
1   ucrtbase!free           0x5e4c5eab  
2   LIBEAY32!CRYPTO_free            0x5e5a123e  

Main.cpp

#include <QCoreApplication>
#include "CNetworkHandleTest.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CNetworkHandleTest net;
    net.start();

    return a.exec();
}

CNetworkHandleTest.cpp

#include "CNetworkHandleTest.h"

CNetworkHandleTest::CNetworkHandleTest()
{
    m_Manager = new QNetworkAccessManager(this);
    // Connect the network manager so we can handle the reply
    connect(m_Manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
    m_nTotalBytes = 0;
}

CNetworkHandleTest::~CNetworkHandleTest()
{
    disconnect();
    m_Manager->deleteLater();
}

void CNetworkHandleTest::onFinished(QNetworkReply* reply)
{
    // Look at reply error
    // Called when all the data is receivedqDebug() << "Error code:" << reply->error();
    qDebug() << "Error string:" << reply->errorString();
    reply->close();
    reply->deleteLater();
}

void CNetworkHandleTest::start()
{
    // Configure the URL string and then set the URL
    QString sUrl(BASE_URL);
    sUrl.append("/console/5555/upload");
    m_Url.setUrl(sUrl);

    // Make the request object based on our URL
    QNetworkRequest request(m_Url);

    // Set request header (not sure how or why this works, but it works)
    // \todo investigate
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlenconded");

    // Make file object associated with our DB file
    QFile file("/tx_database.db");
    if(!file.open(QIODevice::ReadOnly))
    {
        qDebug() << "Failed to open file";
    }

    // Read the entire file as a binary blob
    QByteArray data(file.readAll());

    // Set our request to our request object
    // Note: there should probably be a flag so that when start is called it does not do
    // any processing in case we are already in the middle of processing a request
    m_Request = request;

    // Send it
    m_Reply = m_Manager->post(m_Request, data);

    // Need to connect the signals and slots to the new reply object (manager makes a new
    // reply object every post; may need to investigate if memory should be freed when
    // done processing a response)
    connect(m_Reply,    SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    connect(m_Manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
            this,       SLOT(onAuthenticationRequired(QNetworkReply*, QAuthenticator*)));
}

void CNetworkHandleTest::onReadyRead()
{
    // Whenever data becomes available, this slot is called.  It is called every time data
    // is available, not when all the data has been received.  It is our responsibility to
    // keep track of how much we have received if we want to show progress or whatever
    // but we do not need to keep track if we have received all the data.  The slot
    // OnFinished will be called when the all the data has been received.

    qDebug() << "Bytes available:" << m_Reply->bytesAvailable();
    m_nTotalBytes += m_Reply->bytesAvailable();
    qDebug() << "Bytes thus far:" << m_nTotalBytes;
    QByteArray responseData = m_Reply->readAll();
    qDebug() << "Response" << responseData;
    m_Reply->size();
}

void CNetworkHandleTest::onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator)
{

}

CNetworkHandleTest.h

#ifndef CNETWORKHANDLETEST_H
#define CNETWORKHANDLETEST_H

// Required packages
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QByteArray>
#include <QUrlQuery>
#include <QHostInfo>
#include <QObject>
#include <QUrl>

// Helper packages
#include <QCoreApplication>
#include <QFile>
#include <QDir>

// Our packages
#include <iostream>
#include <fstream>
#include <cstdlib>

#define BASE_URL "localhost:5000"
#define BOUNDARY "123456787654321"

class CNetworkHandleTest : public QObject
{
    Q_OBJECT

public:
    CNetworkHandleTest();
    ~CNetworkHandleTest();

    void start();

protected Q_SLOTS:

    void onFinished(QNetworkReply* reply);
    void onReadyRead();
    void onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator);

private:

    QNetworkAccessManager* m_Manager;
    QNetworkRequest       m_Request;
    QNetworkReply*        m_Reply;
    QUrl                  m_Url;

    int                m_nTotalBytes;
};

#endif // CNETWORKHANDLETEST_H
vincedjango
  • 1,022
  • 1
  • 13
  • 24
  • You could emit a signal from `onFinished()` and connect that to the application's `quit()` slot. That should end the application after the transfer has been completed. Currently closing the application might trigger a form of termination that results in the crash – Kevin Krammer Nov 19 '16 at 17:43
  • I noticed that I get the segfault only on Windows, on Linux it's fine. On Windows I use QT 5.5 and MinGW32. I still want to know why. – vincedjango Nov 19 '16 at 18:07
  • 1
    The base code is probably not handling the signal or whatever Windows is using to terminate the program when a terminal is closed. On Linux that will likely be a standard signal and cause the application to terminate gracefully, while on Windows it might not. Found this while searching for Window's way of signalling terminal closing: http://stackoverflow.com/questions/20511182/catch-windows-terminal-closing-on-running-process – Kevin Krammer Nov 19 '16 at 18:17
  • 1
    Note that the `deleteLater` of `m_manager` is unnecessary: the instance will be deleted from within `QObject::~QObject`. Do observe that the destructor of `CNetworkHandleTest` runs when there's no event loop running anymore, so *iff* `m_manager` didn't have a parent, it'd leak. Do not use `deleteLater` unless you can state a clear reason for doing so. The only typical reason is within slots invoked from a given object: it is undefined behavior (per C++ standard) to do what amounts to `delete sender()` if the connection is automatic, I.e. `delete x` is wrong iff `x == sender()`. – Kuba hasn't forgotten Monica Nov 19 '16 at 18:51
  • 1
    Also the call to `disconnect()` is unnecessary. The whole point of `QObject` is to make life easy: it's **OK** to delete objects that have parents, or that have active connection. It's also OK to hold them by value :) – Kuba hasn't forgotten Monica Nov 19 '16 at 18:57
  • 1
    Also the call to `reply->close()` is unnecessary. Proper `QIODevice`-derived classes are indeed proper C++ classes: they are *always* destructible. The whole point of them is to manage resources automatically, and make your code correct by construction. You can't forget to close a `QFile` nor a `QNetworkReply`: you don't need to close it to begin with! Just destroy the object. – Kuba hasn't forgotten Monica Nov 19 '16 at 18:58
  • Also, when posting test cases, try to minimize them. Instead of the dozens of includes, all you needed was `#include `. That includes everything in the network module and its dependencies (e.g. the core module). You also don't need anything but a single file. And you don't need to put the functionality in a class: it'll work just fine in the body of `main`, using lambdas for short local functions. – Kuba hasn't forgotten Monica Nov 21 '16 at 13:52

1 Answers1

1

When you close the console, your program dies in a most ungraceful manner. You need to write some code to make it graceful instead: The below is a complete test case:

// https://github.com/KubaO/stackoverflown/tree/master/questions/network-cleanup-40695076
#include <QtNetwork>
#include <windows.h>

extern "C" BOOL WINAPI handler(DWORD)
{
   qDebug() << "bye world";
   qApp->quit();
   return TRUE;
}

int main(int argc, char *argv[])
{
   SetConsoleCtrlHandler(&handler, TRUE);
   QCoreApplication a(argc, argv);

   QNetworkAccessManager mgr;
   int totalBytes = 0;

   QObject::connect(&mgr, &QNetworkAccessManager::finished, [](QNetworkReply *reply){
      qDebug() << "Error string:" << reply->errorString();
   });

   QNetworkRequest request(QUrl{"http://www.google.com"});
   request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlenconded");

   auto reply = mgr.post(request, QByteArray{"abcdefgh"});
   QObject::connect(reply, &QIODevice::readyRead, [&]{
      qDebug() << "Bytes available:" << reply->bytesAvailable();
      totalBytes += reply->bytesAvailable();
      qDebug() << "Bytes thus far:" << totalBytes;
      reply->readAll();
   });
   QObject::connect(reply, &QObject::destroyed, []{
      qDebug() << "reply gone";
   });
   QObject::connect(&mgr, &QObject::destroyed, []{
      qDebug() << "manager gone";
   });
   return a.exec();
}

If you press Ctrl-C or click [x] on the console window, the shutdown is orderly, the output being:

[...]
bye world
reply gone
manager gone
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313