c++qtqtnetwork

Get requests canceled while posting with QNetworkAccessManager. How to trigger requests concurrently?


I am developing this software (in C++/Qt5.15.2, on Ubuntu 20.04) that exchanges with an electronic card via HTTP requests. I have HTTP requests to get the status from the device and also some to post files, move or delete them. I am using QNetworkAccessManager to handle my post/get operations.

The problem I'm facing is the following : When I'm not posting, everything is going fine, my status requests work fine and I get the status of the card. When I'm posting (uploading a file), all the requests I send to get the status are canceled.

When I read the documentation, I see :

Note: QNetworkAccessManager queues the requests it receives. The number of requests executed in parallel is dependent on the protocol. Currently, for the HTTP protocol on desktop platforms, 6 requests are executed in parallel for one host/port combination.

From that, I understand that even though I'm posting I should still be able to send status requests and receive an answer. But It's not the case, Did I misunderstood something on the usage of QNetworkAccessManager ?

Here's the code that sends status requests (every 2 sec) that works fine when I'm not posting :

    //Prepare request
    QUrl url = QUrl::fromUserInput(m_url+"/rr_model");
    QUrlQuery query;
    query.addQueryItem("flags", "d99fn");
    url.setQuery(query.query());
    QNetworkRequest request(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json"));

    //Send request
    QNetworkReply* reply = m_networkAccessManager->get(request);
    connect(reply, &QNetworkReply::finished, this, [this,reply]() {

        if(reply->error() == QNetworkReply::NoError)
        {
            QString answer = reply->readAll();
            QJsonDocument jsonResponse = QJsonDocument::fromJson(answer.toUtf8());
            QJsonObject jsonAnswer = jsonResponse.object();
            QJsonObject result = jsonAnswer["result"].toObject();
            if(result.isEmpty())
                return;

            ...Parsing...
        }
        else // handle error
        {
            handleError(reply);
        }
        reply->deleteLater();
    });
    return true;

And here is the post request :

//Get file data
QString filename = QFileInfo(fileToUploadPath).fileName();
QFile toUpload(fileToUploadPath);
qint32 crc = computeCRC32(fileToUploadPath);
if(!toUpload.open(QIODevice::ReadOnly))
    return false;
QByteArray data = toUpload.readAll();
toUpload.close();

//prepare the request
QUrl url = QUrl::fromUserInput(m_url+"/rr_upload");
QUrlQuery query;
QFileInfo info(fileToUploadPath);
QString crcStr = QString::number(crc,16).toLower().right(8);
query.addQueryItem("name", (uploadPath+info.fileName()));
query.addQueryItem("crc32",crcStr);
url.setQuery(query);

QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/octet-steam"));
request.setRawHeader("Accept", "*/*");
m_uploading = true;
QNetworkReply* reply = m_networkAccessManager->post(request,data);
connect(reply, &QNetworkReply::finished, this, [this,reply,filename,fileToUploadPath,uploadPath,commandToExecuterAfterCompletion]() {
    m_uploading = false;
    if(reply->error() == QNetworkReply::NoError)
    {
        QString answer = reply->readAll();
        QJsonDocument jsonResponse = QJsonDocument::fromJson(answer.toUtf8());
        bool err = (jsonResponse["err"].toInt(1))==1;
        if(err){
            logToFile(QString("Upload of file %1 to %2 failed (bad crc ?)").arg(fileToUploadPath,uploadPath));
            emit signalUploadProgressionUpdated(filename,100,false);
        }else{
            logToFile(QString("Upload of file %1 to %2 successful").arg(fileToUploadPath,uploadPath));
            emit signalUploadProgressionUpdated(filename,100,true);
        }

        ...Some code to update the content of the card...
    }
    else // handle error
    {
        logToFile(QString("Upload of file %1 to %2 failed").arg(fileToUploadPath,uploadPath));
        handleError(reply);
        emit signalUploadProgressionUpdated(filename,0,false);
    }
    reply->deleteLater();
});

connect(reply, &QNetworkReply::uploadProgress, this, [this,filename](qint64 sent, qint64 total) {
    double percent = total>0 ? sent*1.0/total*100.0 : 0;
    emit signalUploadProgressionUpdated(filename,percent,true);
});
return true;

Once this post request is triggered, all my get status requests will get an error (handleError is triggered, and I just print the error message) : Operation Canceled Error

Am I doing something wrong ? I thought that Qt would handle the concurrency on itself, but am I supposed to trigger the post request in a separated thread ?

QNetworkAccessManager has his own thread where I send the requests and get the replies. The rest of the software is updated via signals and slots.

An app in Vue.js is distributed with the card, and when I look on Wireshark I see that when it's posting it can still exchange get requests with the card. The problem doesn't come from the HTTP server. If I use Wireshark on my software, I see that the requests stop when the post request starts, and nothing happens until the completion of the post request.

Thanks in advance

Edit

I found the problem. I have an update thread that sends 5 updates commands then waits a second before triggering the update again. My 5 updates commands were async commands, which means that even before then could be finished I was already sending new ones, thus overloading the QNetworkAccessManager. I changed theses commands to sync commands (wait for the previous to return before sending the next) and the problem was solved.


Solution

  • From Qt documentation

    QNetworkReply::OperationCanceledError
    

    means the operation was canceled via calls to abort() or close() before it was finished.

    So, somewhere the socket gets closed. If you don't do it explicitly, then this is done implicitly when the request object gets destroyed.

    Try to run this code by commenting out

    reply->deleteLater();
    

    Another thing, the limit of 6 concurrent connections won't cancel the connection. It will be queued instead.