Details
-
Bug
-
Resolution: Done
-
P2: Important
-
4.7.1
-
None
-
Windows 7 / XP
Description
Hello
We (Jakub Bogacz, Piotr Gumienny) investigated QT source precisely QtNetwork part.
Here are our thoughts:
1. At first, we are sending post request to the server, which is replying and immediately after sending reply it is closing connection.
2. After close connection from host side, Qt is also closing socket and setting error: QAbstractSocket::RemoteHostClosedError
Everything starts in method:
qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength) from qnativesocketengine_win.cpp file.
in which we can see:
if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) { int err = WSAGetLastError(); WS_ERROR_DEBUG(err); switch (err) { case WSAEWOULDBLOCK: ret = -2; break; case WSAEBADF: case WSAEINVAL: //error string is now set in read(), not here in nativeRead() break; case WSAECONNRESET: case WSAECONNABORTED: // for tcp sockets this will be handled in QNativeSocketEngine::read ret = 0; break; default: break; } } else { if (WSAGetLastError() == WSAEWOULDBLOCK) ret = -2; else ret = qint64(bytesRead); }
So if there is no socket error, but bytesRead variable has value 0 (which means connection closed by server - http://msdn.microsoft.com/en-us/library/ms741688(v=vs.85).aspx ) we are also returning 0 as it is in the case of WSAECONNRESET or WSAECONNABORTED error.
This return value is returned to qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) method from qnativesocketengin.cpp file.
qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) { Q_D(QNativeSocketEngine); Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -1); Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); qint64 readBytes = d->nativeRead(data, maxSize); // Call to nativeRead method mentioned above. // Handle remote close if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) { d->setError(QAbstractSocket::RemoteHostClosedError, QNativeSocketEnginePrivate::RemoteHostClosedErrorString); close(); return -1; } else if (readBytes == -1) { if (!d->hasSetSocketError) { d->hasSetSocketError = true; d->socketError = QAbstractSocket::NetworkError; d->socketErrorString = qt_error_string(); } close(); return -1; } return readBytes; }
As you can see if readBytes variable has value 0 the error QAbstractSocket::RemoteHostClosedError is set.
After that the error signal is emitted from socket and received by void QHttpSocketEngine::slotSocketError(QAbstractSocket::SocketError error) method, from qhttpsocketengine.cpp file.
Interesting part is:
d->state = None; setError(error, d->socket->errorString()); if (error == QAbstractSocket::RemoteHostClosedError) { emitReadNotification(); } else { qDebug() << "QHttpSocketEngine::slotSocketError: got weird error =" << error; }
We are setting QHttpSocketEngine object to state None.
In the meantime it is called method void QHttpSocketEngine::slotSocketReadNotification() which should send notification that new part of bytes is ready to read from socket.
In this method we can see:
void QHttpSocketEngine::slotSocketReadNotification(){ Q_D(QHttpSocketEngine); if (d->state != Connected && d->socket->bytesAvailable() == 0) return; if (d->state == Connected) { // Forward as a read notification. if (d->readNotificationEnabled) emitReadNotification(); return; }
So in the case if we still have bytes to read, but our state is None (connection closed by server), read notification is never send to upper layers, so we are unable to read bytes delivered to socket.
This case sometimes occurs so we had an freeze after one, two or three parts of packets that was delivered to our soap client test application.
Example:
Completed: 1122 / 4058
Ready!
Completed: 2549 / 4058
Ready!
And then it is freezing.
Our draft fix proposal is to replace
if (d->state == Connected) { // Forward as a read notification. if (d->readNotificationEnabled) emitReadNotification(); return; }
with:
if (d->state == Connected || ( d->state == None && d->socket->bytesAvailable() > 0 ) ) { // Forward as a read notification. if (d->readNotificationEnabled) emitReadNotification(); return; }
This has been tested and working!