diff options
author | Mårten Nordheim <[email protected]> | 2024-02-22 14:56:19 +0100 |
---|---|---|
committer | Mårten Nordheim <[email protected]> | 2024-03-08 15:09:55 +0000 |
commit | 25df9bbbe1a255b6085dfd283e2c48929b3836ae (patch) | |
tree | d3e6d2bf6e3423ed88d33298e0cf22b7b544570d | |
parent | 3231c458b1a5957d24217cf3e13b6f2fcecd4288 (diff) |
UDP: don't disable read notification unless we have a datagram
The current logic that we will disable the read notification if we
have any data at all doesn't make sense for users who use the
receiveDatagram functionality, since they will not make any calls
that trigger the read notifier to be re-enabled unless there is a
datagram ready for us to hand back.
Changes in this cherry-pick:
- Changed 10s chrono literal to 10'000
Changes in the 6.5 cherry-pick:
- Changed from sleep(20ms) to msleep(20)
Fixes: QTBUG-105871
Change-Id: I0a1f1f8babb037d923d1124c2603b1cb466cfe18
Reviewed-by: Thiago Macieira <[email protected]>
(cherry picked from commit b2ff0c2dc25f640a31fa170dd7cd8964bbcd51d6)
Reviewed-by: Qt Cherry-pick Bot <[email protected]>
(cherry picked from commit 65406e0be7d5d2b687e3e07930ad60bcae893471)
(cherry picked from commit fef939cf879f691b8f487c56897ba54e1c696a1c)
Reviewed-by: Edward Welbourne <[email protected]>
-rw-r--r-- | src/network/socket/qabstractsocket.cpp | 8 | ||||
-rw-r--r-- | src/network/socket/qabstractsocket_p.h | 1 | ||||
-rw-r--r-- | src/network/socket/qudpsocket.cpp | 2 | ||||
-rw-r--r-- | tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp | 75 |
4 files changed, 84 insertions, 2 deletions
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index 841e3efd626..37244dff8f0 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -636,11 +636,15 @@ bool QAbstractSocketPrivate::canReadNotification() return !q->isReadable(); } } else { - if (hasPendingData) { + const bool isUdpSocket = (socketType == QAbstractSocket::UdpSocket); + if (hasPendingData && (!isUdpSocket || hasPendingDatagram)) { socketEngine->setReadNotificationEnabled(false); return true; } - hasPendingData = true; + if (!isUdpSocket || socketEngine->hasPendingDatagrams()) { + hasPendingData = true; + hasPendingDatagram = isUdpSocket; + } } emitReadyRead(); diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h index 074f83c5bb3..e3d6fcffba3 100644 --- a/src/network/socket/qabstractsocket_p.h +++ b/src/network/socket/qabstractsocket_p.h @@ -110,6 +110,7 @@ public: qint64 readBufferMaxSize = 0; bool isBuffered = false; bool hasPendingData = false; + bool hasPendingDatagram = false; QTimer *connectTimer = nullptr; diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp index 1884134c06c..58a9e40dbc6 100644 --- a/src/network/socket/qudpsocket.cpp +++ b/src/network/socket/qudpsocket.cpp @@ -430,6 +430,7 @@ QNetworkDatagram QUdpSocket::receiveDatagram(qint64 maxSize) qint64 readBytes = d->socketEngine->readDatagram(result.d->data.data(), maxSize, &result.d->header, QAbstractSocketEngine::WantAll); d->hasPendingData = false; + d->hasPendingDatagram = false; d->socketEngine->setReadNotificationEnabled(true); if (readBytes < 0) { d->setErrorAndEmit(d->socketEngine->error(), d->socketEngine->errorString()); @@ -479,6 +480,7 @@ qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *addres } d->hasPendingData = false; + d->hasPendingDatagram = false; d->socketEngine->setReadNotificationEnabled(true); if (readBytes < 0) { if (readBytes == -2) { diff --git a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp index 4380433899e..d33fd661d3f 100644 --- a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp +++ b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp @@ -11,6 +11,7 @@ #endif #include <QScopeGuard> #include <QVersionNumber> +#include <QSemaphore> #include <qcoreapplication.h> #include <qfileinfo.h> @@ -103,6 +104,8 @@ private slots: void asyncReadDatagram(); void writeInHostLookupState(); + void readyReadConnectionThrottling(); + protected slots: void empty_readyReadSlot(); void empty_connectedSlot(); @@ -1908,5 +1911,77 @@ void tst_QUdpSocket::writeInHostLookupState() QVERIFY(!socket.putChar('0')); } +void tst_QUdpSocket::readyReadConnectionThrottling() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + + // QTBUG-105871: + // We have some signal/slot connection throttling in QAbstractSocket, but it + // was caring about the bytes, not about the datagrams. + // Test that we don't disable read notifications until we have at least one + // datagram available. Otherwise our good users who use the datagram APIs + // can get into scenarios where they no longer get the readyRead signal + // unless they call a read function once in a while. + + QUdpSocket receiver; + QVERIFY(receiver.bind(QHostAddress(QHostAddress::LocalHost), 0)); + + QSemaphore semaphore; + + // Repro-ing deterministically eludes me, so we are bruteforcing it: + // The thread acts as a remote sender, flooding the receiver with datagrams, + // and at some point the receiver would get into the broken state mentioned + // earlier. + std::unique_ptr<QThread> thread(QThread::create([&semaphore, port = receiver.localPort()]() { + QUdpSocket sender; + sender.connectToHost(QHostAddress(QHostAddress::LocalHost), port); + QCOMPARE(sender.state(), QUdpSocket::ConnectedState); + + constexpr qsizetype PayloadSize = 242; + const QByteArray payload(PayloadSize, 'a'); + + semaphore.acquire(); // Wait for main thread to be ready + while (true) { + // We send 100 datagrams at a time, then sleep. + // This is mostly to let the main thread catch up between bursts so + // it doesn't get stuck in the loop. + for (int i = 0; i < 100; ++i) { + [[maybe_unused]] + qsizetype sent = sender.write(payload); + Q_ASSERT(sent > 0); + } + if (QThread::currentThread()->isInterruptionRequested()) + break; + QThread::msleep(20); + } + })); + thread->start(); + auto threadStopAndWaitGuard = qScopeGuard([&thread] { + thread->requestInterruption(); + thread->quit(); + thread->wait(); + }); + + qsizetype count = 0; + QObject::connect(&receiver, &QUdpSocket::readyRead, &receiver, + [&] { + while (receiver.hasPendingDatagrams()) { + receiver.readDatagram(nullptr, 0); + ++count; + } + // If this prints `false, xxxx` we were pretty much guaranteed + // that we would not get called again: + // qDebug() << receiver.hasPendingDatagrams() << receiver.bytesAvailable(); + }, + Qt::QueuedConnection); + + semaphore.release(); + constexpr qsizetype MaxCount = 500; + QVERIFY2(QTest::qWaitFor([&] { return count >= MaxCount; }, 10'000), + QByteArray::number(count).constData()); +} + QTEST_MAIN(tst_QUdpSocket) #include "tst_qudpsocket.moc" |