PDA

View Full Version : QNetworkAccessManager smart handling of timeouts / network errors



frankiefrank
16th May 2011, 15:39
I'm working with QNetworkAccessManager and I have some cases in which the whole application hangs after the get() call if there was no network connection. (For example if I don't allow the outgoing connection via the firewall dialog).

My implementation supports multiple active downloads. I've seen some online advice (here / StackOverflow I think) about using timers to check the downloadProgress. But is there really no better way to do this?

Sorry if I'm being vague - I'd just like to hear about other possible solutions.

wysota
17th May 2011, 00:26
QNetworkAccessManager shouldn't behave like that. Please show us your code.

frankiefrank
17th May 2011, 15:07
Thanks for your constant assistance.

I have a wrapper for QNetworkAccessManager and a small DownloadInfo object wrapping the QNetworkReply objects.

My scenario is the following: after the download call is run my firewall asks me whether to allow the connection or not. If I choose to block it, the application hangs.

In my constructor I connect to the finished signal which is not fired in this case:



connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));



void NetAccessWrapper::download(const QString &url, const QString &destinationPath)
{
QUrl urlObj;
QNetworkRequest request;
urlObj.setUrl(url, QUrl::StrictMode);
request.setUrl(urlObj);
QNetworkReply *reply = manager->get(request);

// Just in case I'm connecting to the error signal which is not emitted
bool ok = connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));

// Container for the reply object
DownloadInfoWrapper *downloadInfo = new DownloadInfoWrapper(reply);
downloadInfo->url = url;
downloadInfo->targetFileName = destinationPath;

// QMap of download info objects
activeDownloads.insert(url, downloadInfo);

// This signal is also not fired in this case
connect(downloadInfo, SIGNAL(downloadProgress(QString, qint64, qint64)), this, SLOT(downloadProgress(QString, qint64, qint64)));
}


Added after 22 minutes:

In case this is relevant, the whole thing is called from a modal dialog.

I see some posts about connecting to a readyRead signal.. but my issue is how can I tell if this signal is never emitted. I see waitForReadyRead is not implemented for QNetworkReply so I'm a bit lost.

wysota
17th May 2011, 15:45
And how do you know your application hangs? Do the windows get repainted when you obscure them with other windows?

frankiefrank
17th May 2011, 15:49
The windows do get repainted but the UI is blocked and the taskbar icon doesn't minimize/restore the window. Both the main window and the dialog from within I'm running the network related code do not respond.

wysota
17th May 2011, 16:13
The windows do get repainted
So your application doesn't hang if it is still executing code.


but the UI is blocked and the taskbar icon doesn't minimize/restore the window. Both the main window and the dialog from within I'm running the network related code do not respond.

Use a debugger to see where the control flow in your application is at the moment when you experience this behaviour.

frankiefrank
17th May 2011, 17:03
I apologize, perhaps I shouldn't have used the word "hang" - what is definite is that the UI is stuck and I can't do anything to cause a signal to be emitted. I can't close the dialog or click a button. I wanted to implement a timer to check occasionally the status of my reply objects, but that timer didn't fire the timeout() signal as well.

If I pause at different moments during this "freeze" I get a different place. Callstack is very long. Here's a sample:



QtCored4.dll!QLocalePrivate::stringToUnsLongLong(c onst QString & number="127", int base=10, bool * ok=0x0018d587, QLocalePrivate::GroupSeparatorMode group_sep_mode=FailOnGroupSeparators) Line 4501 + 0x1 bytes C++
QtCored4.dll!QString::toULongLong(bool * ok=0x0018d5d3, int base=10) Line 5540 + 0x1d bytes C++
QtCored4.dll!QString::toUInt(bool * ok=0x0018d5d3, int base=10) Line 5667 + 0x10 bytes C++
QtNetworkd4.dll!parseIp4(const QString & address="127.0.0.1", unsigned int * addr=0x0018d614) Line 166 + 0x1a bytes C++
QtNetworkd4.dll!QHostAddressPrivate::parse() Line 280 + 0xd bytes C++
QtNetworkd4.dll!QHostAddress::setAddress(const QString & address="127.0.0.1") Line 655 C++
QtNetworkd4.dll!QHostAddress::QHostAddress(QHostAd dress::SpecialAddress address=LocalHost) Line 546 + 0x28 bytes C++
QtNetworkd4.dll!QHostAddress::operator==(QHostAddr ess::SpecialAddress other=LocalHost) Line 839 + 0xc bytes C++
QtNetworkd4.dll!QNativeSocketEnginePrivate::checkP roxy(const QHostAddress & address={...}) Line 282 + 0xa bytes C++
QtNetworkd4.dll!QNativeSocketEngine::connectToHost (const QHostAddress & address={...}, unsigned short port=80) Line 521 + 0xc bytes C++
QtNetworkd4.dll!QNativeSocketEngine::connectionNot ification() Line 546 C++
QtNetworkd4.dll!QWriteNotifier::event(QEvent * e=0x0018dc6c) Line 1134 C++
QtGuid4.dll!QApplicationPrivate::notify_helper(QOb ject * receiver=0x02a19560, QEvent * e=0x0018dc6c) Line 4462 + 0x11 bytes C++
QtGuid4.dll!QApplication::notify(QObject * receiver=0x02a19560, QEvent * e=0x0018dc6c) Line 3862 + 0x10 bytes C++
QtCored4.dll!QCoreApplication::notifyInternal(QObj ect * receiver=0x02a19560, QEvent * event=0x0018dc6c) Line 731 + 0x15 bytes C++
> QtCored4.dll!QCoreApplication::sendEvent(QObject * receiver=0x02a19560, QEvent * event=0x0018dc6c) Line 215 + 0x39 bytes C++
QtCored4.dll!qt_internal_proc(HWND__ * hwnd=0x004608fe, unsigned int message=1024, unsigned int wp=1572, long lp=656211984) Line 485 + 0xf bytes C++
user32.dll!762362fa()
[Frames below may be incorrect and/or missing, no symbols loaded for user32.dll]
user32.dll!76236d3a()
user32.dll!76236ce9()
user32.dll!762377c4()
user32.dll!7623788a()
QtCored4.dll!QEventDispatcherWin32::processEvents( QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 813 C++
QtGuid4.dll!QGuiEventDispatcherWin32::processEvent s(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 1170 + 0x15 bytes C++
QtCored4.dll!QEventLoop::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 150 C++
QtCored4.dll!QEventLoop::exec(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 201 + 0x2d bytes C++
QtCored4.dll!QCoreApplication::exec() Line 1008 + 0x15 bytes C++
QtGuid4.dll!QApplication::exec() Line 3737 C++
myApp.exe!main(int argc=1, char * * argv=0x00349a18) Line 157 + 0x6 bytes C++
myApp.exe!_WinMain@16() + 0x7a bytes
myApp.exe!__tmainCRTStartup() Line 578 + 0x35 bytes C
myApp.exe!WinMainCRTStartup() Line 403 C
kernel32.dll!75f333ca()
ntdll.dll!774c9ed2()
ntdll.dll!774c9ea5()
myApp.exe!MyContentInteractive::metaObject() Line 55 + 0x1f bytes C++
ffff648d()


Another one:



user32.dll!762360e2()
[Frames below may be incorrect and/or missing, no symbols loaded for user32.dll]
user32.dll!762360e2()
user32.dll!76243958()
> QtCored4.dll!qt_GetMessageHook(int code=0, unsigned int wp=1, long lp=1629528) Line 518 + 0xb bytes C++
user32.dll!76246381()
user32.dll!762380a9()
user32.dll!76238ba1()
ntdll.dll!774a011a()
user32.dll!76240735()
user32.dll!762406eb()
user32.dll!76240751()
QtCored4.dll!QEventDispatcherWin32::processEvents( QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 745 + 0x15 bytes C++
QtGuid4.dll!QGuiEventDispatcherWin32::processEvent s(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 1170 + 0x15 bytes C++
QtCored4.dll!QEventLoop::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 150 C++
QtCored4.dll!QEventLoop::exec(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...}) Line 201 + 0x2d bytes C++
QtCored4.dll!QCoreApplication::exec() Line 1008 + 0x15 bytes C++
QtGuid4.dll!QApplication::exec() Line 3737 C++
MyApp.exe!main(int argc=1, char * * argv=0x00349a18) Line 157 + 0x6 bytes C++
MyApp.exe!_WinMain@16() + 0x7a bytes
MyApp.exe!__tmainCRTStartup() Line 578 + 0x35 bytes C
MyApp.exe!WinMainCRTStartup() Line 403 C
kernel32.dll!75f333ca()
ntdll.dll!774c9ed2()
ntdll.dll!774c9ea5()
MyApp.exe!MyContentInteractive::metaObject() Line 55 + 0x1f bytes C++
ffff648d()



Added after 41 minutes:

I've recreated the problem using no wrappers:

In my app header file:


//private:
QNetworkAccessManager *mManager;
QNetworkReply *reply;

//private slots:
void authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator);
void replyFinished(QNetworkReply *reply);
void downloadProgress(QString url, qint64 bytesReceived, qint64 bytesTotal);
void onError(QNetworkReply::NetworkError code);


In my code file:


// in constructor:
mManager = new QNetworkAccessManager(this);
bool connected = connect(mManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
connected = connect(mManager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
this, SLOT(authenticationRequired(QNetworkReply*, QAuthenticator*)));

// My update action implementation:
void MyApp::on_actionUpdate_triggered() {
QUrl urlObj;
QNetworkRequest request;
urlObj.setUrl("http://www.google.com/logos/2011/paraguay11-hp.jpg", QUrl::StrictMode);
request.setUrl(urlObj);
reply = mManager->get(request);
bool ok = connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
}

// And some slots:
void MyApp::authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
{
qDebug() << DBGTIME << "auth";
}

void MyApp::replyFinished(QNetworkReply *reply)
{
qDebug() << DBGTIME << "finished";
}

void MyApp::downloadProgress(QString url, qint64 bytesReceived, qint64 bytesTotal)
{
qDebug() << DBGTIME << "downloadProgress";
}

void MyApp::onError(QNetworkReply::NetworkError code)
{
qDebug() << DBGTIME << "error";
}

wysota
17th May 2011, 20:32
It looks like events are processed, everything should work fine. The request should eventually timeout. If you want someone else to test your program, please provide a minimal compilable example reproducing the problem.

frankiefrank
17th May 2011, 21:59
Thank you, I'll try to do this later and update here in the thread.

frankiefrank
18th May 2011, 15:21
I couldn't upload the .zip file here because of size limitations, but here's a link to download it:
https://rapidshare.com/files/240484025/NetAccessDemo.zip

This project has a simple window with a button. The constructor initializes QNetworkAccessManager and connects to the finished/authentication required signal. Generated QNetworkReply objects' error signals are also connected to.

I have Norton Internet Security installed. When the network request is activated a window asks me whether to allow the connection. If I deny it, I get the described behavior.

Note: this is only if I *block* the connection, if there's no network connection to begin with the behavior is as expected (the error signal is emitted, followed by a finished signal).

wysota
18th May 2011, 15:26
Are you aware what a minimal example is? How come it didn't fit into the forum's limitations? How much code is there? 100 files?

Please reupload your project and make sure it only contains source files -- no executable and no intermediate files. We need four files at max -- a .pro file, a main.cpp file and optionally one .cpp and one .h file for any classes you want to implement. If you really have to, provide a .ui file too.

frankiefrank
18th May 2011, 16:20
I'm sorry, I work with VS2008, took too many files into the zip. Should be fine now.

wysota
18th May 2011, 17:20
I have a different firewall than you do so my equivalent of you clicking your firewall button is the following three rules:
iptables -I OUTPUT -p tcp -d 74.125.77.99 -j DROP
iptables -I OUTPUT -p tcp -d 74.125.77.147 -j DROP
iptables -I OUTPUT -p tcp -d 74.125.77.104 -j DROP

... where the three IP addresses are what the dns returns for www.google.com.

After I run the program and click the "Connect" button, I don't experience any weird behaviour (actually I don't experience any feedback at all) -- I can resize and move the window as usual. Is there something else that should be happening?

frankiefrank
18th May 2011, 22:12
When exactly do you call these commands?

If it's before running the application, this is weird because this means the finished() signal is not emitted even though there's definitely a get() call.

To simulate my case, if you have a popup for the firewall question, this should occur after the get() is called. THEN if I'm not allowing the connection I'd get the UI being stuck as I described.

wysota
18th May 2011, 22:30
When exactly do you call these commands?
Before running the application, of course.


If it's before running the application, this is weird because this means the finished() signal is not emitted even though there's definitely a get() call.
Why would it be emitted?


To simulate my case, if you have a popup for the firewall question, this should occur after the get() is called. THEN if I'm not allowing the connection I'd get the UI being stuck as I described.
The popup is meaningless. What's important is that no packets go through and no information is returned to the transmitting application. Remember that your firewall semantics is "deny/drop unless accepted" and not "accept unless denied", so you have "drop" by default and only accepting the popup causes an "accept" (the packets don't go through the firewall between the time the popup shows and you click the "allow" button).

Try using wireshark to monitor the exact traffic between your application and the firewall to see if the firewall informes your app that it drops the connection if you want. But whatever the firewall does, it shouldn't prevent the application from processing events.

frankiefrank
18th May 2011, 22:52
Why would it be emitted?


If I understand the documentation, if a request fails (in this case because of the firewal) it should cause one or more of these signals to be emitted: error / finished / downloadProgress (with 0 value for both bytes recieved and total).

That's definitely the case for me if there's no network to begin with - you get an error signal with the code "host not found".



But whatever the firewall does, it shouldn't prevent the application from processing events.


That sounds sensible, but it's definitely the behavior I'm experiencing. I'll try monitoring the traffic as you suggest but I'm not sure what I'm looking for. Could this be an issue specific to Qt and Norton Internet Security?

wysota
18th May 2011, 23:39
If I understand the documentation, if a request fails (in this case because of the firewal) it should cause one or more of these signals to be emitted: error / finished / downloadProgress (with 0 value for both bytes recieved and total).
The point is the application has no idea that no packets went through. The firewall would have to return a "connection refused" packet to the application. iptables doesn't do that with its DROP policy (REJECT would have done that). The application will learn of the failure only when the network operation times out and the system notifies us about it (usually after 5-10 minutes depending on the system settings).


That's definitely the case for me if there's no network to begin with - you get an error signal with the code "host not found".
That's a different situation.


That sounds sensible, but it's definitely the behavior I'm experiencing.
I'm not so sure since your windows get redrawn.

Could this be an issue specific to Qt and Norton Internet Security?
I'd say this was specific to Norton Internet Security. I wouldn't be suprised if another firewall behaved differently. Maybe it's a matter of tweaking the settings.

frankiefrank
19th May 2011, 10:01
Can I open a bug with Qt if it's suspected as a compatibility issue with a product?

wysota
19th May 2011, 10:25
Can I open a bug with Qt if it's suspected as a compatibility issue with a product?

You can do that, sure. But first verify that is indeed the case. So far you are only assuming so.

krsmichael
20th February 2013, 00:40
My guess is that your QT library was built against SSL. QNetworkAccessManager looks for the ssl dlls, libeay32.dll and ssleay32.dll. The Win32 LoadLibrary hangs the entire process, UI, signals slots, everything, while it attempts to load these dlls. Copy these dlls to your executable's directory and the loads will quickly succeed.

qknight
4th September 2013, 13:26
i'm probably having a similar issue to what is discussed here, please also see:
https://qt-project.org/forums/viewthread/11763

i've created a minimal QCoreApplication example and engineered a network setup which is reproducible on your system, so in a nutshell: you can now reproduce this bug, see:
https://github.com/qknight/qt-QNetworkAccessManager-issues/tree/c4e69219a40551829a930b25149f2ef661d4dcb1

*there is one difference though*:

i had this issue also with *QNetworkAccessManager* but after playing with the issue for a while (/etc/resolv.conf) i now think it is DNS related so i was looking into *QHostInfo::lookupHost*

but the *README.MD* contains a detailed description of the setup and tests i was using/doing.

i'm using:
* qt-4.8.4
* nixos linux (nixos.org)
* kernel 3.4.56

some experiments
i made some experiments with iptables and since DNS uses UDP on my system (not TCP as shown in the rules above) i modified them slightly:

iptables -D OUTPUT -d 8.8.8.8 -j DROP

surprisingly this rule made the shutdown process, which would normally timeout with 40 seconds, succeed in no time (say 5ms). for some reason a local process just knows that the DNS resolver fails.

gethostip shows the same behaviour:

% time gethostip lastlog.de
lastlog.de: Unknown host
gethostip lastlog.de 0.00s user 0.00s system 75% cpu 0.005 total

summary: using the iptables rule does not help to reproduce the issue but hides it instead! so better use this setup to reproduce it:

host pc ---- switch1 ---- switch2 ----- internet

and then unplug switch2 so that networkmanager/ifplugd (or similar) won't notice that the cable has been unplugged.

anyway, have a look at the README.MD file in the repo above. as described there i will now try to modify the Qt toolkit to ignore the DNS resoler on shutdown - maybe that is possible.

feedback welcome. my jabber: flux@jabber.ccc.de

best wishes,
joachim schiele

wysota
4th September 2013, 14:15
Does the same thing happen when using code that doesn't make use of Qt? If no, what are the differences in code and behavior, if yes - why are you asking this question relating it to Qt?

qknight
8th September 2013, 15:26
i've made some traces and an minimal example to illustrate the problem, my repo on github.com linked below.

discussion

qt-QNetworkAccessManager-issues discussion:
https://github.com/qknight/qt-QNetworkAccessManager-issues/tree/a43f6f03e6b7899c6cff28c2f9df729a7f885593#discussio n

possible solution(s)

qt-QNetworkAccessManager-issues solution(s):
https://github.com/qknight/qt-QNetworkAccessManager-issues/tree/a43f6f03e6b7899c6cff28c2f9df729a7f885593#possible-solutions

summary: in a nutshell

Qt's getaddrinfo(..) abstraction must be changed to make the lookupHost(..) call _really_ async. right now it is sync, when the network connection between client and DNS server is interrupted, which isn't that seldom as one might expect.

should i fill a bug report? this problem isn't solved at all! ;-)

qknight
8th September 2013, 22:41
i’ve added a new bug here, hope this is the right tracker!
https://bugreports.qt-project.org/browse/QTBUG-33391
thanks

te777
12th April 2018, 23:32
@krsmichael Thank you so much! Adding those dlls worked for me. It seems Qt will write empty files if there is a network error. I was checking for this in my app to isolate the problem. My app downloads from an https site. Worked on my development PC (Windows 10) because Qt was in the path of the user and the files are located in the folder QtCreator/bin. But running my app in VirtualBox failed because it couldn't find the dlls. Thanks again so much!