QNAM: Fix reply deadlocks on server closing connection
authorMarkus Goetz <markus@woboq.com>
Fri, 24 Jul 2015 07:53:20 +0000 (09:53 +0200)
committerMarkus Goetz (Woboq GmbH) <markus@woboq.com>
Thu, 20 Aug 2015 12:02:32 +0000 (12:02 +0000)
The _q_readyRead can also be called from readMoreLater() because we implemented
it so that bandwidth limited reading can be implemented.
This can lead to a race condition if the socket is closing at the specific moment
and then deadlock the channel: It will stay unusable with a zombie request.
The fix in QHttpProtocolaHandler checks if there is actually bytes available to read
from the socket and only then continue.

The fix in the HTTP channel needs to be done to properly finish the reply in
cases of a server replying with HTTP/1.0 or "Connection: close".
The delayed incovation of _q_receiveReply will properly finish up the reply.

Change-Id: I19ce2ae595f91d56386cc7406ccacc9935672b6b
Reviewed-by: Richard J. Moore <rich@kde.org>
src/network/access/qhttpnetworkconnectionchannel.cpp
src/network/access/qhttpprotocolhandler.cpp

index 7428f9bf5bdcf8b9765654b2fa6d2e5d95781e46..257aa13718220d06a2ebf5835a6a7cf49505e251 100644 (file)
@@ -829,11 +829,15 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
 
             if (!reply->d_func()->expectContent()) {
                 // No content expected, this is a valid way to have the connection closed by the server
+                // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
+                QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
                 return;
             }
             if (reply->contentLength() == -1 && !reply->d_func()->isChunked()) {
                 // There was no content-length header and it's not chunked encoding,
                 // so this is a valid way to have the connection closed by the server
+                // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
+                QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
                 return;
             }
             // ok, we got a disconnect even though we did not expect it
index ab2e3dac57a71679c446f42e3c0c23199f62f2fe..a2083158f1ddaec9d7ddb3b62803730026f04b65 100644 (file)
@@ -237,7 +237,12 @@ void QHttpProtocolHandler::_q_readyRead()
     }
 
     if (m_channel->isSocketWaiting() || m_channel->isSocketReading()) {
-        m_channel->state = QHttpNetworkConnectionChannel::ReadingState;
+        if (m_socket->bytesAvailable()) {
+            // We might get a spurious call from readMoreLater()
+            // call of the QHttpNetworkConnection even while the socket is disconnecting.
+            // Therefore check if there is actually bytes available before changing the channel state.
+            m_channel->state = QHttpNetworkConnectionChannel::ReadingState;
+        }
         if (m_reply)
             _q_receiveReply();
     }