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

QSerialPortPrivate::writeData function may cause memory exhaustion.

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P3: Somewhat important
    • 6.10.0 FF
    • 6.8.3, 6.9.0
    • Serial Port
    • None
    • window11
    • Windows
    • 2
    • 07d21cb06 (dev)
    • Foundation Sprint 129, Foundation Sprint 130

    Description

       

      qint64 QSerialPortPrivate::writeData(const char *data, qint64 maxSize)
      {
          Q_Q(QSerialPort);
          writeBuffer.append(data, maxSize);    
            if (!writeBuffer.isEmpty() && !writeStarted) {
              if (!startAsyncWriteTimer) {
                  startAsyncWriteTimer = new QTimer(q);
                  QObjectPrivate::connect(startAsyncWriteTimer, &QTimer::timeout, this, &QSerialPortPrivate::_q_startAsyncWrite);
                  startAsyncWriteTimer->setSingleShot(true);
              }
              if (!startAsyncWriteTimer->isActive())
                  startAsyncWriteTimer->start();
          }
          return maxSize;
      } 
      ... 
      bool QSerialPortPrivate::_q_startAsyncWrite()
      {
          if (handle == INVALID_HANDLE_VALUE) {
              // Prevent a crash if the application incorrectly has not called open() before
              // calling this (QTBUG-120412)
              return false;
          }    
            if (writeBuffer.isEmpty() || writeStarted)
              return true;    
            writeChunkBuffer = writeBuffer.read();
          ::ZeroMemory(&writeCompletionOverlapped, sizeof(writeCompletionOverlapped));
          if (!::WriteFile(handle, writeChunkBuffer.constData(),
                           writeChunkBuffer.size(), nullptr, &writeCompletionOverlapped)) {        
                  QSerialPortErrorInfo error = getSystemError();
              if (error.errorCode != QSerialPort::NoError) {
                  if (error.errorCode != QSerialPort::ResourceError)
                      error.errorCode = QSerialPort::WriteError;
                  setError(error);
                  return false;
              }
          }    writeStarted = true;
          return true;
      }

       

      writeBuffer.append(data, maxSize) continuously consumes memory. If writeCompletionOverlapped does not return in a timely manner, writeStarted will remain true, which prevents _q_startAsyncWrite from being executed. As a result, writeBuffer.append(data, maxSize) will keep growing until memory is exhausted.

      My application uses Windows asynchronous I/O (Overlapped I/O) to write data to the serial port. However, the virtual serial port driver (Launch Virtual Serial Port Driver) does not fully or correctly support the Overlapped I/O mechanism in Windows. This causes a critical issue: the OVERLAPPED structure is never signaled, meaning that the write completion is never acknowledged. As a result, the write buffer keeps accumulating data, eventually consuming excessive memory and potentially causing system instability.

      To mitigate this issue from the application side, I propose introducing a write buffer size limit to prevent uncontrolled memory growth. For example:

       

      qint64 QSerialPortPrivate::writeData(const char *data, qint64 maxSize)
      {
          Q_Q(QSerialPort);
          int max_m_size = 4;
          while (writeBuffer.size() >= qint64(1024 * 1024 * max_m_size)) {
              (void)writeBuffer.read();  // Drop the oldest data to limit memory usage
          }
          writeBuffer.append(data, maxSize);
          if (!writeBuffer.isEmpty() && !writeStarted) {
              if (!startAsyncWriteTimer) {
                  startAsyncWriteTimer = new QTimer(q);
                  QObjectPrivate::connect(startAsyncWriteTimer, &QTimer::timeout,
                                          this, &QSerialPortPrivate::_q_startAsyncWrite);
                  startAsyncWriteTimer->setSingleShot(true);
              }
              if (!startAsyncWriteTimer->isActive())
                  startAsyncWriteTimer->start();
          }
          return maxSize;
      }
      

      This change introduces a soft cap on the write buffer size (e.g., 4 MB) and discards the oldest data when the buffer exceeds this threshold. Although it doesn't solve the root issue—i.e., the Overlapped I/O write not completing—it does prevent the buffer from growing indefinitely, which is especially important when using third-party virtual serial drivers with incomplete asynchronous support.

      Attachments

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

        Activity

          People

            ivan.solovev Ivan Solovev
            jixianglongpersonal Ji Xianglong
            Vladimir Minenko Vladimir Minenko
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes