From 378e26dd14df808a55471330400984841ef414d4 Mon Sep 17 00:00:00 2001 From: Alex Trotsenko Date: Wed, 11 Feb 2015 20:02:07 +0200 Subject: [PATCH] QUdpSocket: avoid infinite read notifier blocking There was a small amount of time between the last readDatagram() call and disabling a read notifier in case the socket had a pending datagram. If a new datagram arrived in this period, this qualified as absence of a datagram reader. Do not change the read notifier state because it is disabled on canReadNotification() entry and always enabled by the datagram reader. Thanks to Peter Seiderer, who investigated the same: "Querying hasPendingDatagrams() for enabling/disabling setReadNotificationEnabled() is racy (a new datagram could arrive after readDatagam() is called and before hasPendingDatagrams() is checked). But for unbuffered sockets the ReadNotification is already disabled before the readReady signal is emitted and should be re-enabled when calling read() or readDatagram() from the user." However, this patch does not completely solve the problem under Windows, as the socket notifier may emit spurious notifications. Task-number: QTBUG-46552 Change-Id: If7295d53ae2c788c39e86303502f38135c4d6180 Reviewed-by: Oswald Buddenhagen Reviewed-by: Thiago Macieira --- src/network/socket/qabstractsocket.cpp | 12 ++--- .../socket/qudpsocket/tst_qudpsocket.cpp | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp index c42b4f520c..d93150799c 100644 --- a/src/network/socket/qabstractsocket.cpp +++ b/src/network/socket/qabstractsocket.cpp @@ -739,15 +739,9 @@ bool QAbstractSocketPrivate::canReadNotification() return true; } - if (socketEngine) { - // turn the socket engine off if we've either: - // - got pending datagrams - // - reached the buffer size limit - if (isBuffered) - socketEngine->setReadNotificationEnabled(readBufferMaxSize == 0 || readBufferMaxSize > q->bytesAvailable()); - else if (socketType != QAbstractSocket::TcpSocket) - socketEngine->setReadNotificationEnabled(!socketEngine->hasPendingDatagrams()); - } + // turn the socket engine off if we've reached the buffer size limit + if (socketEngine && isBuffered) + socketEngine->setReadNotificationEnabled(readBufferMaxSize == 0 || readBufferMaxSize > q->bytesAvailable()); // reset the read socket notifier state if we reentered inside the // readyRead() connected slot. diff --git a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp index b6129bec08..a4695955af 100644 --- a/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp +++ b/tests/auto/network/socket/qudpsocket/tst_qudpsocket.cpp @@ -116,10 +116,12 @@ private slots: void linkLocalIPv4(); void readyRead(); void readyReadForEmptyDatagram(); + void asyncReadDatagram(); protected slots: void empty_readyReadSlot(); void empty_connectedSlot(); + void async_readDatagramSlot(); private: #ifndef QT_NO_BEARERMANAGEMENT @@ -127,6 +129,8 @@ private: QNetworkConfiguration networkConfiguration; QSharedPointer networkSession; #endif + QUdpSocket *m_asyncSender; + QUdpSocket *m_asyncReceiver; }; static QHostAddress makeNonAny(const QHostAddress &address, QHostAddress::SpecialAddress preferForAny = QHostAddress::LocalHost) @@ -1671,5 +1675,55 @@ void tst_QUdpSocket::readyReadForEmptyDatagram() QCOMPARE(receiver.readDatagram(buf, sizeof buf), qint64(0)); } +void tst_QUdpSocket::async_readDatagramSlot() +{ + char buf[1]; + QVERIFY(m_asyncReceiver->hasPendingDatagrams()); + QCOMPARE(m_asyncReceiver->pendingDatagramSize(), qint64(1)); + QCOMPARE(m_asyncReceiver->bytesAvailable(), qint64(1)); + QCOMPARE(m_asyncReceiver->readDatagram(buf, sizeof(buf)), qint64(1)); + + if (buf[0] == '2') { + QTestEventLoop::instance().exitLoop(); + return; + } + + m_asyncSender->writeDatagram("2", makeNonAny(m_asyncReceiver->localAddress()), m_asyncReceiver->localPort()); + // wait a little to ensure that the datagram we've just sent + // will be delivered on receiver side. + QTest::qSleep(100); +} + +void tst_QUdpSocket::asyncReadDatagram() +{ + QFETCH_GLOBAL(bool, setProxy); + if (setProxy) + return; + + m_asyncSender = new QUdpSocket; + m_asyncReceiver = new QUdpSocket; +#ifdef FORCE_SESSION + m_asyncSender->setProperty("_q_networksession", QVariant::fromValue(networkSession)); + m_asyncReceiver->setProperty("_q_networksession", QVariant::fromValue(networkSession)); +#endif + + QVERIFY(m_asyncReceiver->bind(QHostAddress(QHostAddress::AnyIPv4), 0)); + quint16 port = m_asyncReceiver->localPort(); + QVERIFY(port != 0); + + QSignalSpy spy(m_asyncReceiver, SIGNAL(readyRead())); + connect(m_asyncReceiver, SIGNAL(readyRead()), SLOT(async_readDatagramSlot())); + + m_asyncSender->writeDatagram("1", makeNonAny(m_asyncReceiver->localAddress()), port); + + QTestEventLoop::instance().enterLoop(1); + + QVERIFY(!QTestEventLoop::instance().timeout()); + QCOMPARE(spy.count(), 2); + + delete m_asyncSender; + delete m_asyncReceiver; +} + QTEST_MAIN(tst_QUdpSocket) #include "tst_qudpsocket.moc"