Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

    Update: Forum Guidelines & Code of Conduct

    Solved QTcpSocket::read() reads 0 bytes and it shouldn't: why?

    General and Desktop
    5
    11
    2055
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      Bart_Vandewoestyne last edited by

      While debugging some mysterious network communication problem in our legacy codebase (Qt 4.8.7 on Windows), I was able to trim it down to the following:

      1. First, we use waitForReadyRead() to check that there is new data available for reading from the QTcpSocket.
      2. Next, once we get past waitForReadyRead(), there is new data available and just to make sure there are actually bytes there, I check with bytesAvailable() which tells me that there are 14 bytes available for reading.
      3. In our legacy code base, the data is then read as follows:
        char theByte = '\0';
        while (theByte != someEndFlag)
        {
            const auto bytesRead = read(&theByte, 1);
            ....
        }
        

      Note that the original authors decided to read one byte at a time. I believe this is not optimal, but for as far as I know, it should work. The mysterious thing happening here, is that bytesRead is 0, which is definitely not what I expect, because almost right before the read statement, bytesAvailable() tells me there are 14 bytes available! So why am I not reading the first of those 14 bytes then?

      Oh, and by the way... this fails in a release build, but works in a debug build. For release builds where we show more log output, either to the console or to a file, it also works. So the problem only occurs in our 'fastest' setup, being a release build without output to the console or logfiles.

      aha_1980 jsulm 2 Replies Last reply Reply Quote 0
      • aha_1980
        aha_1980 Lifetime Qt Champion @Bart_Vandewoestyne last edited by aha_1980

        Hi @Bart_Vandewoestyne

        1. I hope you use waitForReadyRead() in a separate thread, not in the main thread. That will most likely not work.
        2. Have you set a timeout for waitForReadyRead()? From your description, it sounds like the whole code runs faster than you expect and is only slowed down by the debug prints - and therefore you don't see the bug when you enable the debug output.

        Qt has to stay free or it will die.

        1 Reply Last reply Reply Quote 5
        • jsulm
          jsulm Lifetime Qt Champion @Bart_Vandewoestyne last edited by

          @Bart_Vandewoestyne said in QTcpSocket::read() reads 0 bytes and it shouldn't: why?:

          while (theByte != someEndFlag)

          I'm wondering what will happen if you read past these 14 bytes without getting someEndFlag? You will keep calling read(), right? And I think that's why you get bytesRead = 0 - because there is nothing more to read. This code really needs to be fixed:

          while (theByte != someEndFlag)
          {
              const auto bytesRead = read(&theByte, 1);
              if (bytesRead == 0)
                 break;
              ....
          }
          

          https://forum.qt.io/topic/113070/qt-code-of-conduct

          aha_1980 B 2 Replies Last reply Reply Quote 2
          • aha_1980
            aha_1980 Lifetime Qt Champion @jsulm last edited by

            @jsulm said in QTcpSocket::read() reads 0 bytes and it shouldn't: why?:

            I'm wondering what will happen if you read past these 14 bytes without getting someEndFlag?

            That is a very good point! Well spotted.

            Qt has to stay free or it will die.

            1 Reply Last reply Reply Quote 1
            • J.Hilk
              J.Hilk Moderators last edited by

              I'm kinda confused, why, when reading a single char, the buffer is read in a while loop?
              I mean the while loop either does not enter or breaks after the first cycle.

              Is this for compiler optimization?

              qint64 QIODevice::read(char *data, qint64 maxSize)
              {
                  Q_D(QIODevice);
              
              #if defined QIODEVICE_DEBUG
                  printf("%p QIODevice::read(%p, %lld), d->pos = %lld, d->buffer.size() = %lld\n",
                         this, data, maxSize, d->pos, d->buffer.size());
              #endif
              
                  const bool sequential = d->isSequential();
              
                  // Short-cut for getChar(), unless we need to keep the data in the buffer.
                  if (maxSize == 1 && !(sequential && d->transactionStarted)) {
                      int chint;
                      while ((chint = d->buffer.getChar()) != -1) {
                          if (!sequential)
                              ++d->pos;
              
                          char c = char(uchar(chint));
                          if (c == '\r' && (d->openMode & Text))
                              continue;
                          *data = c;
              #if defined QIODEVICE_DEBUG
                          printf("%p \tread 0x%hhx (%c) returning 1 (shortcut)\n", this,
                                 int(c), isprint(c) ? c : '?');
              #endif
                          if (d->buffer.isEmpty())
                              readData(data, 0);
                          return qint64(1);
                      }
                  }
              
                  CHECK_MAXLEN(read, qint64(-1));
                  CHECK_READABLE(read, qint64(-1));
              
                  const qint64 readBytes = d->read(data, maxSize);
              
              #if defined QIODEVICE_DEBUG
                  printf("%p \treturning %lld, d->pos == %lld, d->buffer.size() == %lld\n", this,
                         readBytes, d->pos, d->buffer.size());
                  if (readBytes > 0)
                      debugBinaryString(data - readBytes, readBytes);
              #endif
              
                  return readBytes;
              }
              

              Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct

              Qt Needs YOUR vote: https://bugreports.qt.io/browse/QTQAINFRA-4121


              Q: What's that?
              A: It's blue light.
              Q: What does it do?
              A: It turns blue.

              B 1 Reply Last reply Reply Quote 0
              • B
                Bart_Vandewoestyne @jsulm last edited by

                @jsulm said in QTcpSocket::read() reads 0 bytes and it shouldn't: why?:

                I'm wondering what will happen if you read past these 14 bytes without getting someEndFlag? You will keep calling read(), right?

                We first call waitForReadyRead again. The whole code looks more or less as follows (logging statements removed):

                char theByte = '\0';
                while (theByte != static_cast<char>(someEndFlag))
                {
                	const auto bytesRead = read(&theByte, 1);
                	if (bytesRead > 0)
                	{
                		buffer.append(theByte);
                		nbBytesRead++;
                	}
                	else  // error (-1) or no more data (0)
                	{
                		if (!waitForReadyRead(timeout))
                		{
                			buffer.clear();
                			break;
                		}
                	}
                }
                

                In the meanwhile, we think we've found the root cause of the problem. I will explain that in another reply in this discussion.

                1 Reply Last reply Reply Quote 0
                • B
                  Bart_Vandewoestyne @J.Hilk last edited by

                  @J.Hilk said in QTcpSocket::read() reads 0 bytes and it shouldn't: why?:

                  I'm kinda confused, why, when reading a single char, the buffer is read in a while loop?
                  I mean the while loop either does not enter or breaks after the first cycle.

                  Is this for compiler optimization?

                  I'm afraid I do not completely understand your question. I've posted more code in another reply. Does that make things more clear for you? Note also that we think we've found the root cause of the problem. I will explain that in another reply in this discussion.

                  J.Hilk 1 Reply Last reply Reply Quote 0
                  • J.Hilk
                    J.Hilk Moderators @Bart_Vandewoestyne last edited by

                    @Bart_Vandewoestyne
                    hi, my comment was about the actually read function read(char *data, qint64 maxSize) , I posted the code frrom the QIODevice source code, your read ist most likly based on.
                    Can't thay for sure as I don't know the base class, but I would asume it to be QIODevice. As it is the case for nearly all read/write functions in Qt :-)

                    Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct

                    Qt Needs YOUR vote: https://bugreports.qt.io/browse/QTQAINFRA-4121


                    Q: What's that?
                    A: It's blue light.
                    Q: What does it do?
                    A: It turns blue.

                    kshegunov 1 Reply Last reply Reply Quote 0
                    • B
                      Bart_Vandewoestyne last edited by Bart_Vandewoestyne

                      We think we found the root cause of our problem. After some googling, I came across this Stack Overflow post: https://stackoverflow.com/questions/16653117/why-cant-my-code-read-the-number-of-bytes-available-on-a-qtcpsocket-ideas

                      The code I've posted, is part of a function mfReadDataFromDevice() which is a member function of a FooBarSocket class that is a subclass of QTcpSocket. To check the threads in which FooBarSocket::mfReadDataFromDevice() is called, and the thread in which the read() is called (= the thread where our socket object lives), I've added some logging statements right before the read() statement (before the while loop), where I print the value of QThread::currentThread() and thread().

                      And now here comes the clue: in some of our unit tests, these threads appeared to be the same, but in the failing unit test, these threads appeared to be different.

                      On top of that, we started to experience this problem after changing a signal/slot connection to a direct function call. The signal was only connected to a single slot, and we therefore thought we could simply replace the signal/slot connection by a direct function call. But there, we overlooked that the thread in which the socket lives could be different from the thread where FooBarSocket::mfReadDataFromDevice() is called. Once we reverted that change, the unit test no longer failed.

                      So our main conclusion would be: always make sure that the socket your are reading from belongs to the same thread on which you want to do the reading.

                      My remaining questions are:

                      • Do you guys think that our conclusion is right?
                      • This seems to be something to pay attention to. Is this something that is documented for QTcpSockets? Or is it something to pay attention to for even more general things, not only when working with QTcpSockets? I would be happy to have some more URLs/references to documentation/blogposts/other websites that talk about these pitfalls.
                      • Given what we experienced/osberved, are there differences in behavior between Qt 4.8.7 (what we currently use) and Qt 5.X?

                      Kind regards,
                      Bart

                      1 Reply Last reply Reply Quote 1
                      • kshegunov
                        kshegunov Moderators @J.Hilk last edited by

                        @J.Hilk
                        I'd bet that's because of the text-mode translation for windows, see the comparison with '\r'

                        @Bart_Vandewoestyne said in QTcpSocket::read() reads 0 bytes and it shouldn't: why?:

                        • Do you guys think that our conclusion is right?

                        If your investigation in the threading is correct, which I have no reason to doubt, then yes, this does sound right.

                        • This seems to be something to pay attention to. Is this something that is documented for QTcpSockets? Or is it something to pay attention to for even more general things, not only when working with QTcpSockets? I would be happy to have some more URLs/references to documentation/blogposts/other websites that talk about these pitfalls.

                        Well, accessing objects, sockets or otherwise, from different threads has to be protected (serialized in some way). With signal slot connections Qt does that through the event loop, if you do it manually you have to use a mutex or a wait condition. That is if the class is not explicitly documented as thread safe (at the top of the class page)

                        • Given what we experienced/osberved, are there differences in behavior between Qt 4.8.7 (what we currently use) and Qt 5.X?

                        If the reason you deduced is correct, the behaviour you get is undefined, as this is a race condition. So whether or not there's a difference will vary (or not) between Qt version, compiler and OS.

                        Read and abide by the Qt Code of Conduct

                        1 Reply Last reply Reply Quote 3
                        • B
                          Bart_Vandewoestyne last edited by

                          Thanks for all the help an suggestions here. I always like it when I learn from mistakes, either my own or mistakes from others :-) Considering this closed now.

                          1 Reply Last reply Reply Quote 0
                          • First post
                            Last post