Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-21351

When TCP connection is closed by the host data is not sent to the listener (QNetworkReply)

    XMLWordPrintable

Details

    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!

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            Unassigned Unassigned
            gumiepio Piotr Gumienny
            Votes:
            1 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes