obtain hosts information without threading

Signed-off-by: Ivailo Monev <xakepa10@laimg.moc>
This commit is contained in:
Ivailo Monev 2019-11-21 00:12:36 +00:00
parent bfade642ba
commit df7225de36
4 changed files with 36 additions and 580 deletions

View file

@ -41,27 +41,15 @@
#include "qhostinfo.h"
#include "qhostinfo_p.h"
#include "QtCore/qscopedpointer.h"
#include "qabstracteventdispatcher.h"
#include "qcoreapplication.h"
#include "qmetaobject.h"
#include "qstringlist.h"
#include "qthread.h"
#include "qurl.h"
#include "qnetworksession_p.h"
#ifdef Q_OS_UNIX
# include <unistd.h>
#endif
QT_BEGIN_NAMESPACE
//#define QHOSTINFO_DEBUG
// #define QHOSTINFO_DEBUG
#ifndef QT_NO_THREAD
Q_GLOBAL_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
#endif
Q_GLOBAL_STATIC(QHostInfoCache, globalHostInfoCache)
/*!
\class QHostInfo
@ -107,12 +95,9 @@ Q_GLOBAL_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
To retrieve the name of the local host, use the static
QHostInfo::localHostName() function.
\note Since Qt 4.6.1 QHostInfo is using multiple threads for DNS lookup
instead of one dedicated DNS thread. This improves performance,
but also changes the order of signal emissions when using lookupHost()
compared to previous versions of Qt.
\note Since Qt 4.6.3 QHostInfo is using a small internal 60 second DNS cache
\note Since 4.6.3 QHostInfo is using a small internal 60 second DNS cache
for performance improvements.
\note Since 4.9.0 QHostInfo is not using multiple threads for DNS lookup.
\sa QAbstractSocket, {http://www.rfc-editor.org/rfc/rfc3492.txt}{RFC 3492}
*/
@ -151,16 +136,11 @@ static QAtomicInt theIdCounter = QAtomicInt(1);
int QHostInfo::lookupHost(const QString &name, QObject *receiver,
const char *member)
{
#if defined QHOSTINFO_DEBUG
#ifdef QHOSTINFO_DEBUG
qDebug("QHostInfo::lookupHost(\"%s\", %p, %s)",
name.toLatin1().constData(), receiver, member ? member + 1 : 0);
#endif
if (!QAbstractEventDispatcher::instance(QThread::currentThread())) {
qWarning("QHostInfo::lookupHost() called with no event dispatcher");
return -1;
}
qRegisterMetaType<QHostInfo>("QHostInfo");
if (!receiver)
@ -169,48 +149,20 @@ int QHostInfo::lookupHost(const QString &name, QObject *receiver,
int id = theIdCounter.fetchAndAddRelaxed(1); // generate unique ID
if (name.isEmpty()) {
QHostInfo hostInfo(id);
hostInfo.setError(QHostInfo::HostNotFound);
hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given"));
QScopedPointer<QHostInfoResult> result(new QHostInfoResult);
QObject::connect(result.data(), SIGNAL(resultsReady(QHostInfo)),
receiver, member, Qt::QueuedConnection);
result.data()->emitResultsReady(hostInfo);
return id;
}
#ifndef QT_NO_THREAD
QHostInfoLookupManager *manager = theHostInfoLookupManager();
if (manager) {
// the application is still alive
if (manager->cache.isEnabled()) {
// check cache first
bool valid = false;
QHostInfo info = manager->cache.get(name, &valid);
if (valid) {
info.setLookupId(id);
QHostInfo info(id);
info.setError(QHostInfo::HostNotFound);
info.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given"));
QHostInfoResult result;
QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection);
QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member);
result.emitResultsReady(info);
return id;
}
}
// cache is not enabled or it was not in the cache, do normal lookup
QHostInfoRunnable* runnable = new QHostInfoRunnable(name, id);
if (receiver)
QObject::connect(&runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection);
manager->scheduleLookup(runnable);
}
#else
QHostInfo hostInfo(id);
hostInfo.fromName(name);
QScopedPointer<QHostInfoResult> result(new QHostInfoResult);
QObject::connect(result.data(), SIGNAL(resultsReady(QHostInfo)),
receiver, member, Qt::QueuedConnection);
result.data()->emitResultsReady(hostInfo);
#endif // QT_NO_THREAD
QHostInfo info(id);
info.fromName(name);
QHostInfoResult result;
QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member);
result.emitResultsReady(info);
return id;
}
@ -222,9 +174,7 @@ int QHostInfo::lookupHost(const QString &name, QObject *receiver,
*/
void QHostInfo::abortHostLookup(int id)
{
#ifndef QT_NO_THREAD
theHostInfoLookupManager()->abortLookup(id);
#endif
// nothing to do
}
/*!
@ -242,43 +192,18 @@ void QHostInfo::abortHostLookup(int id)
*/
QHostInfo QHostInfo::fromName(const QString &name)
{
#if defined QHOSTINFO_DEBUG
#ifdef QHOSTINFO_DEBUG
qDebug("QHostInfo::fromName(\"%s\")",name.toLatin1().constData());
#endif
QHostInfo hostInfo = QHostInfoAgent::fromName(name);
#ifndef QT_NO_THREAD
QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
manager->cache.put(name, hostInfo);
#endif
QHostInfoCache* cache = globalHostInfoCache();
if (cache && cache->isEnabled()) {
cache->put(name, hostInfo);
}
return hostInfo;
}
#ifndef QT_NO_BEARERMANAGEMENT
QHostInfo QHostInfoPrivate::fromName(const QString &name, QSharedPointer<QNetworkSession> session)
{
#if defined QHOSTINFO_DEBUG
qDebug("QHostInfoPrivate::fromName(\"%s\") with session %p",name.toLatin1().constData(), session.data());
#endif
QHostInfo hostInfo = QHostInfoAgent::fromName(name, session);
#ifndef QT_NO_THREAD
QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
manager->cache.put(name, hostInfo);
#endif
return hostInfo;
}
#endif
#ifndef QT_NO_BEARERMANAGEMENT
// This function has a special implementation for symbian right now in qhostinfo_symbian.cpp but not on other OS.
QHostInfo QHostInfoAgent::fromName(const QString &hostName, QSharedPointer<QNetworkSession>)
{
return QHostInfoAgent::fromName(hostName);
}
#endif
/*!
\enum QHostInfo::HostInfoError
@ -454,285 +379,21 @@ void QHostInfo::setErrorString(const QString &str)
\sa hostName()
*/
#ifndef QT_NO_THREAD
QAbstractHostInfoLookupManager* QAbstractHostInfoLookupManager::globalInstance()
{
return theHostInfoLookupManager();
}
QHostInfoRunnable::QHostInfoRunnable(QString hn, int i) : toBeLookedUp(hn), id(i)
{
setAutoDelete(true);
}
// the QHostInfoLookupManager will at some point call this via a QThreadPool
void QHostInfoRunnable::run()
{
QHostInfoLookupManager *manager = theHostInfoLookupManager();
// check aborted
if (manager->wasAborted(id)) {
manager->lookupFinished(this);
return;
}
QHostInfo hostInfo;
// QHostInfo::lookupHost already checks the cache. However we need to check
// it here too because it might have been cache saved by another QHostInfoRunnable
// in the meanwhile while this QHostInfoRunnable was scheduled but not running
if (manager->cache.isEnabled()) {
// check the cache first
bool valid = false;
hostInfo = manager->cache.get(toBeLookedUp, &valid);
if (!valid) {
// not in cache, we need to do the lookup and store the result in the cache
hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
manager->cache.put(toBeLookedUp, hostInfo);
}
} else {
// cache is not enabled, just do the lookup and continue
hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
}
// check aborted again
if (manager->wasAborted(id)) {
manager->lookupFinished(this);
return;
}
// signal emission
hostInfo.setLookupId(id);
resultEmitter.emitResultsReady(hostInfo);
// now also iterate through the postponed ones
{
QMutexLocker locker(&manager->mutex);
QMutableListIterator<QHostInfoRunnable*> iterator(manager->postponedLookups);
while (iterator.hasNext()) {
QHostInfoRunnable* postponed = iterator.next();
if (toBeLookedUp == postponed->toBeLookedUp) {
// we can now emit
iterator.remove();
hostInfo.setLookupId(postponed->id);
postponed->resultEmitter.emitResultsReady(hostInfo);
delete postponed;
}
}
}
manager->lookupFinished(this);
// thread goes back to QThreadPool
}
QHostInfoLookupManager::QHostInfoLookupManager() : mutex(QMutex::Recursive), wasDeleted(false)
{
moveToThread(QCoreApplicationPrivate::mainThread());
connect(QCoreApplication::instance(), SIGNAL(destroyed()), SLOT(waitForThreadPoolDone()), Qt::DirectConnection);
threadPool.setMaxThreadCount(5); // do 5 DNS lookups in parallel
}
QHostInfoLookupManager::~QHostInfoLookupManager()
{
wasDeleted = true;
// don't qDeleteAll currentLookups, the QThreadPool has ownership
clear();
}
void QHostInfoLookupManager::clear()
{
{
QMutexLocker locker(&mutex);
qDeleteAll(postponedLookups);
qDeleteAll(scheduledLookups);
qDeleteAll(finishedLookups);
postponedLookups.clear();
scheduledLookups.clear();
finishedLookups.clear();
}
threadPool.waitForDone();
cache.clear();
}
void QHostInfoLookupManager::work()
{
if (wasDeleted)
return;
// goals of this function:
// - launch new lookups via the thread pool
// - make sure only one lookup per host/IP is in progress
QMutexLocker locker(&mutex);
if (!finishedLookups.isEmpty()) {
// remove ID from aborted if it is in there
for (int i = 0; i < finishedLookups.length(); i++) {
abortedLookups.removeAll(finishedLookups.at(i)->id);
}
finishedLookups.clear();
}
if (!postponedLookups.isEmpty()) {
// try to start the postponed ones
QMutableListIterator<QHostInfoRunnable*> iterator(postponedLookups);
while (iterator.hasNext()) {
QHostInfoRunnable* postponed = iterator.next();
// check if none of the postponed hostnames is currently running
bool alreadyRunning = false;
for (int i = 0; i < currentLookups.length(); i++) {
if (currentLookups.at(i)->toBeLookedUp == postponed->toBeLookedUp) {
alreadyRunning = true;
break;
}
}
if (!alreadyRunning) {
iterator.remove();
scheduledLookups.prepend(postponed); // prepend! we want to finish it ASAP
}
}
}
if (!scheduledLookups.isEmpty()) {
// try to start the new ones
QMutableListIterator<QHostInfoRunnable*> iterator(scheduledLookups);
while (iterator.hasNext()) {
QHostInfoRunnable *scheduled = iterator.next();
// check if a lookup for this host is already running, then postpone
for (int i = 0; i < currentLookups.size(); i++) {
if (currentLookups.at(i)->toBeLookedUp == scheduled->toBeLookedUp) {
iterator.remove();
postponedLookups.append(scheduled);
scheduled = 0;
break;
}
}
if (scheduled && currentLookups.size() < threadPool.maxThreadCount()) {
// runnable now running in new thread, track this in currentLookups
threadPool.start(scheduled);
iterator.remove();
currentLookups.append(scheduled);
} else {
// was postponed, continue iterating
continue;
}
};
}
}
// called by QHostInfo
void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r)
{
if (wasDeleted)
return;
QMutexLocker locker(&this->mutex);
scheduledLookups.enqueue(r);
work();
}
// called by QHostInfo
void QHostInfoLookupManager::abortLookup(int id)
{
if (wasDeleted)
return;
QMutexLocker locker(&this->mutex);
// is postponed? delete and return
for (int i = 0; i < postponedLookups.length(); i++) {
if (postponedLookups.at(i)->id == id) {
delete postponedLookups.takeAt(i);
return;
}
}
// is scheduled? delete and return
for (int i = 0; i < scheduledLookups.length(); i++) {
if (scheduledLookups.at(i)->id == id) {
delete scheduledLookups.takeAt(i);
return;
}
}
if (!abortedLookups.contains(id))
abortedLookups.append(id);
}
// called from QHostInfoRunnable
bool QHostInfoLookupManager::wasAborted(int id)
{
if (wasDeleted)
return true;
QMutexLocker locker(&this->mutex);
return abortedLookups.contains(id);
}
// called from QHostInfoRunnable
void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r)
{
if (wasDeleted)
return;
QMutexLocker locker(&this->mutex);
currentLookups.removeOne(r);
finishedLookups.append(r);
work();
}
#endif // QT_NO_THREAD
// This function returns immediately when we had a result in the cache, else it will later emit a signal
QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id)
{
#ifndef QT_NO_THREAD
*valid = false;
*id = -1;
// check cache
QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
if (manager && manager->cache.isEnabled()) {
QHostInfo info = manager->cache.get(name, valid);
if (*valid) {
return info;
}
}
#endif
// was not in cache, trigger lookup
*id = QHostInfo::lookupHost(name, receiver, member);
// return empty response, valid==false
return QHostInfo();
}
void qt_qhostinfo_clear_cache()
{
#ifndef QT_NO_THREAD
QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
if (manager) {
manager->clear();
QHostInfoCache* cache = globalHostInfoCache();
if (cache) {
cache->clear();
}
#endif
}
void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e)
{
#ifndef QT_NO_THREAD
QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager();
if (manager) {
manager->cache.setEnabled(e);
QHostInfoCache* cache = globalHostInfoCache();
if (cache) {
cache->setEnabled(e);
}
#else
Q_UNUSED(e);
#endif
}
// cache for 60 seconds
@ -761,15 +422,14 @@ QHostInfo QHostInfoCache::get(const QString &name, bool *valid) const
{
*valid = false;
QHostInfoCacheElement *element = cache.object(name);
if (element) {
if (element->age.elapsed() < max_age*1000)
if (element && element->age.elapsed() < max_age*1000) {
*valid = true;
return element->info;
}
// FIXME idea:
// if too old but not expired, trigger a new lookup
// to freshen our cache
}
return QHostInfo();
}

View file

@ -49,14 +49,9 @@
#include "qcoreapplication_p.h"
#include "QtNetwork/qhostinfo.h"
#include "QtCore/qmutex.h"
#include "QtCore/qwaitcondition.h"
#include "QtCore/qobject.h"
#include "QtCore/qpointer.h"
#include "QtCore/qthread.h"
#include "QtCore/qthreadpool.h"
#include "QtCore/qrunnable.h"
#include "QtCore/qlist.h"
#include "QtCore/qqueue.h"
#include <QElapsedTimer>
#include <QCache>
@ -87,10 +82,6 @@ class QHostInfoAgent : public QObject
Q_OBJECT
public:
static QHostInfo fromName(const QString &hostName);
#ifndef QT_NO_BEARERMANAGEMENT
static QHostInfo fromName(const QString &hostName, QSharedPointer<QNetworkSession> networkSession);
#endif
};
class QHostInfoPrivate
@ -102,11 +93,6 @@ public:
lookupId(0)
{
}
#ifndef QT_NO_BEARERMANAGEMENT
//not a public API yet
static QHostInfo fromName(const QString &hostName, QSharedPointer<QNetworkSession> networkSession);
#endif
QHostInfo::HostInfoError err;
QString errorStr;
QList<QHostAddress> addrs;
@ -116,7 +102,6 @@ public:
// These functions are outside of the QHostInfo class and strictly internal.
// Do NOT use them outside of QAbstractSocket.
QHostInfo Q_NETWORK_EXPORT qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id);
void Q_AUTOTEST_EXPORT qt_qhostinfo_clear_cache();
void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e);
@ -142,75 +127,6 @@ private:
QMutex mutex;
};
// the following classes are used for the (normal) case: We use multiple threads to lookup DNS
#ifndef QT_NO_THREAD
class QHostInfoRunnable : public QRunnable
{
public:
QHostInfoRunnable (QString hn, int i);
void run();
QString toBeLookedUp;
int id;
QHostInfoResult resultEmitter;
};
class QAbstractHostInfoLookupManager : public QObject
{
Q_OBJECT
public:
~QAbstractHostInfoLookupManager() {}
virtual void clear() = 0;
QHostInfoCache cache;
protected:
QAbstractHostInfoLookupManager() {}
static QAbstractHostInfoLookupManager* globalInstance();
};
class QHostInfoLookupManager : public QAbstractHostInfoLookupManager
{
Q_OBJECT
public:
QHostInfoLookupManager();
~QHostInfoLookupManager();
void clear();
void work();
// called from QHostInfo
void scheduleLookup(QHostInfoRunnable *r);
void abortLookup(int id);
// called from QHostInfoRunnable
void lookupFinished(QHostInfoRunnable *r);
bool wasAborted(int id);
friend class QHostInfoRunnable;
protected:
QList<QHostInfoRunnable*> currentLookups; // in progress
QList<QHostInfoRunnable*> postponedLookups; // postponed because in progress for same host
QQueue<QHostInfoRunnable*> scheduledLookups; // not yet started
QList<QHostInfoRunnable*> finishedLookups; // recently finished
QList<int> abortedLookups; // ids of aborted lookups
QThreadPool threadPool;
QMutex mutex;
bool wasDeleted;
private slots:
void waitForThreadPoolDone() { threadPool.waitForDone(); }
};
#endif // QT_NO_THREAD
QT_END_NAMESPACE
#endif // QHOSTINFO_P_H

View file

@ -1385,18 +1385,8 @@ void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint
#endif
} else {
if (d->threadData->eventDispatcher) {
// this internal API for QHostInfo either immediately gives us the desired
// QHostInfo from cache or later calls the _q_startConnecting slot.
bool immediateResultValid = false;
QHostInfo hostInfo = qt_qhostinfo_lookup(hostName,
this,
SLOT(_q_startConnecting(QHostInfo)),
&immediateResultValid,
&d->hostLookupId);
if (immediateResultValid) {
d->hostLookupId = -1;
d->_q_startConnecting(hostInfo);
}
d->_q_startConnecting(QHostInfo::fromName(hostName));
}
}
@ -1740,14 +1730,6 @@ bool QAbstractSocket::waitForConnected(int msecs)
#endif
QHostInfo::abortHostLookup(d->hostLookupId);
d->hostLookupId = -1;
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> networkSession;
QVariant v(property("_q_networksession"));
if (v.isValid()) {
networkSession = qvariant_cast< QSharedPointer<QNetworkSession> >(v);
d->_q_startConnecting(QHostInfoPrivate::fromName(d->hostName, networkSession));
} else
#endif
d->_q_startConnecting(QHostInfo::fromName(d->hostName));
}
if (state() == UnconnectedState)

View file

@ -105,12 +105,6 @@ private slots:
void multipleDifferentLookups_data();
void multipleDifferentLookups();
void cache();
void abortHostLookup();
#ifndef QT_NO_THREAD
void abortHostLookupInDifferentThread();
#endif
protected slots:
void resultsReady(const QHostInfo &);
@ -539,49 +533,6 @@ void tst_QHostInfo::multipleDifferentLookups()
QCOMPARE(lookupsDoneCounter, repeats*COUNT);
}
void tst_QHostInfo::cache()
{
QFETCH_GLOBAL(bool, cache);
if (!cache)
return; // test makes only sense when cache enabled
// reset slot counter
lookupsDoneCounter = 0;
// lookup once, wait in event loop, result should not come directly.
bool valid = true;
int id = -1;
QHostInfo result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(valid == false);
QVERIFY(result.addresses().isEmpty());
// loopkup second time, result should come directly
valid = false;
result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id);
#if defined(Q_OS_LINUX)
if (valid != true)
QEXPECT_FAIL("", "QTBUG-28079", Continue);
#endif
QVERIFY(valid == true);
QVERIFY(!result.addresses().isEmpty());
// clear the cache
qt_qhostinfo_clear_cache();
// lookup third time, result should not come directly.
valid = true;
result = qt_qhostinfo_lookup("localhost", this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(valid == false);
QVERIFY(result.addresses().isEmpty());
// the slot should have been called 2 times.
QCOMPARE(lookupsDoneCounter, 2);
}
void tst_QHostInfo::resultsReady(const QHostInfo &hi)
{
lookupDone = true;
@ -590,59 +541,6 @@ void tst_QHostInfo::resultsReady(const QHostInfo &hi)
QMetaObject::invokeMethod(&QTestEventLoop::instance(), "exitLoop", Qt::QueuedConnection);
}
void tst_QHostInfo::abortHostLookup()
{
//reset counter
lookupsDoneCounter = 0;
bool valid = false;
int id = -1;
QHostInfo result = qt_qhostinfo_lookup("a-single" TEST_DOMAIN, this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QVERIFY(!valid);
//it is assumed that the DNS request/response in the backend is slower than it takes to call abort
QHostInfo::abortHostLookup(id);
QTestEventLoop::instance().enterLoop(5);
#if defined(Q_OS_LINUX)
if (lookupsDoneCounter != 0)
QEXPECT_FAIL("", "QTBUG-28079", Continue);
#endif
QCOMPARE(lookupsDoneCounter, 0);
}
#ifndef QT_NO_THREAD
class LookupAborter : public QObject
{
Q_OBJECT
public slots:
void abort()
{
QHostInfo::abortHostLookup(id);
QThread::currentThread()->quit();
}
public:
int id;
};
void tst_QHostInfo::abortHostLookupInDifferentThread()
{
//reset counter
lookupsDoneCounter = 0;
bool valid = false;
int id = -1;
QHostInfo result = qt_qhostinfo_lookup("a-single" TEST_DOMAIN, this, SLOT(resultsReady(QHostInfo)), &valid, &id);
QVERIFY(!valid);
QThread thread;
LookupAborter aborter;
aborter.id = id;
aborter.moveToThread(&thread);
connect(&thread, SIGNAL(started()), &aborter, SLOT(abort()));
//it is assumed that the DNS request/response in the backend is slower than it takes to schedule the thread and call abort
thread.start();
QVERIFY(thread.wait(5000));
QTestEventLoop::instance().enterLoop(5);
QCOMPARE(lookupsDoneCounter, 0);
}
#endif // QT_NO_THREAD
QTEST_MAIN(tst_QHostInfo)
#include "moc_tst_qhostinfo.cpp"