Details
-
Bug
-
Resolution: Fixed
-
P3: Somewhat important
-
6.8.3, 6.9.0
-
None
-
window11
-
-
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.