Fix QNetworkConfigurationManager usage outside main thread first
QNetworkConfigurationManager creates the engines loaded from plugins as objects in the main thread. If a QNetworkConfigurationManager instance is created in a worker thread without any instance previously existing in the main thread, then it is uninitialised until the main thread has run. This causes allConfigurations() to return an empty list if called immediately after instantiation, for example. This fix initialises the plugins using blocking queued connections, which causes the worker thread to block until the initialisation function has been called in the context of the main thread. Deadlock is possible if the main thread is for some reason waiting on the worker thread, but it will not deadlock on QNetworkConfigurationManager's mutex. If this is a problem for an application, it should use QNetworkConfigurationManager from the main thread first to preload the plugins. Task-number: QTBUG-18795 Task-number: QTBUG-18799 Reviewed-by: Cristiano Di Flora
This commit is contained in:
parent
7dd503c62f
commit
a590e77fd8
@ -385,8 +385,6 @@ void QNetworkConfigurationManagerPrivate::updateConfigurations()
|
|||||||
this, SLOT(configurationRemoved(QNetworkConfigurationPrivatePointer)));
|
this, SLOT(configurationRemoved(QNetworkConfigurationPrivatePointer)));
|
||||||
connect(engine, SIGNAL(configurationChanged(QNetworkConfigurationPrivatePointer)),
|
connect(engine, SIGNAL(configurationChanged(QNetworkConfigurationPrivatePointer)),
|
||||||
this, SLOT(configurationChanged(QNetworkConfigurationPrivatePointer)));
|
this, SLOT(configurationChanged(QNetworkConfigurationPrivatePointer)));
|
||||||
|
|
||||||
QMetaObject::invokeMethod(engine, "initialize");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,8 +408,19 @@ void QNetworkConfigurationManagerPrivate::updateConfigurations()
|
|||||||
startPolling();
|
startPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstUpdate)
|
if (firstUpdate) {
|
||||||
firstUpdate = false;
|
firstUpdate = false;
|
||||||
|
QList<QBearerEngine*> enginesToInitialize = sessionEngines; //shallow copy the list in case it is modified when we unlock mutex
|
||||||
|
Qt::ConnectionType connectionType;
|
||||||
|
if (QCoreApplicationPrivate::mainThread() == QThread::currentThread())
|
||||||
|
connectionType = Qt::DirectConnection;
|
||||||
|
else
|
||||||
|
connectionType = Qt::BlockingQueuedConnection;
|
||||||
|
locker.unlock();
|
||||||
|
foreach (QBearerEngine* engine, enginesToInitialize) {
|
||||||
|
QMetaObject::invokeMethod(engine, "initialize", connectionType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QNetworkConfigurationManagerPrivate::performAsyncConfigurationUpdate()
|
void QNetworkConfigurationManagerPrivate::performAsyncConfigurationUpdate()
|
||||||
|
@ -62,6 +62,7 @@ public slots:
|
|||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void usedInThread(); // this test must be first, or it will falsely pass
|
||||||
void allConfigurations();
|
void allConfigurations();
|
||||||
void defaultConfiguration();
|
void defaultConfiguration();
|
||||||
void configurationFromIdentifier();
|
void configurationFromIdentifier();
|
||||||
@ -329,6 +330,43 @@ void tst_QNetworkConfigurationManager::configurationFromIdentifier()
|
|||||||
QVERIFY(!invalid.isValid());
|
QVERIFY(!invalid.isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QNCMTestThread : public QThread
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual void run()
|
||||||
|
{
|
||||||
|
QNetworkConfigurationManager manager;
|
||||||
|
preScanConfigs = manager.allConfigurations();
|
||||||
|
QSignalSpy spy(&manager, SIGNAL(updateCompleted()));
|
||||||
|
manager.updateConfigurations(); //initiate scans
|
||||||
|
QTRY_VERIFY(spy.count() == 1); //wait for scan to complete
|
||||||
|
configs = manager.allConfigurations();
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
QList<QNetworkConfiguration> configs;
|
||||||
|
QList<QNetworkConfiguration> preScanConfigs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// regression test for QTBUG-18795
|
||||||
|
void tst_QNetworkConfigurationManager::usedInThread()
|
||||||
|
{
|
||||||
|
QNCMTestThread thread;
|
||||||
|
connect(&thread, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||||
|
thread.start();
|
||||||
|
QTestEventLoop::instance().enterLoop(5);
|
||||||
|
QVERIFY(thread.isFinished());
|
||||||
|
qDebug() << "prescan:" << thread.preScanConfigs.count();
|
||||||
|
qDebug() << "postscan:" << thread.configs.count();
|
||||||
|
|
||||||
|
QNetworkConfigurationManager manager;
|
||||||
|
QList<QNetworkConfiguration> preScanConfigs = manager.allConfigurations();
|
||||||
|
QSignalSpy spy(&manager, SIGNAL(updateCompleted()));
|
||||||
|
manager.updateConfigurations(); //initiate scans
|
||||||
|
QTRY_VERIFY(spy.count() == 1); //wait for scan to complete
|
||||||
|
QList<QNetworkConfiguration> configs = manager.allConfigurations();
|
||||||
|
QCOMPARE(thread.configs, configs);
|
||||||
|
QCOMPARE(thread.preScanConfigs, preScanConfigs);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QNetworkConfigurationManager)
|
QTEST_MAIN(tst_QNetworkConfigurationManager)
|
||||||
#include "tst_qnetworkconfigurationmanager.moc"
|
#include "tst_qnetworkconfigurationmanager.moc"
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
#include <qcoreapplication.h>
|
#include <qcoreapplication.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qnetworkproxy.h>
|
#include <qnetworkproxy.h>
|
||||||
|
|
||||||
#include <QNetworkConfiguration>
|
#include <QNetworkConfiguration>
|
||||||
#include <QNetworkConfigurationManager>
|
#include <QNetworkConfigurationManager>
|
||||||
#include <QNetworkSession>
|
#include <QNetworkSession>
|
||||||
@ -57,6 +58,8 @@
|
|||||||
Q_DECLARE_METATYPE(QNetworkConfiguration);
|
Q_DECLARE_METATYPE(QNetworkConfiguration);
|
||||||
Q_DECLARE_METATYPE(QList<QNetworkProxy>);
|
Q_DECLARE_METATYPE(QList<QNetworkProxy>);
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
class tst_QNetworkProxyFactory : public QObject {
|
class tst_QNetworkProxyFactory : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -77,6 +80,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void systemProxyForQueryCalledFromThread();
|
||||||
void systemProxyForQuery() const;
|
void systemProxyForQuery() const;
|
||||||
#ifndef QT_NO_BEARERMANAGEMENT
|
#ifndef QT_NO_BEARERMANAGEMENT
|
||||||
void fromConfigurations();
|
void fromConfigurations();
|
||||||
@ -240,5 +244,32 @@ void tst_QNetworkProxyFactory::inNetworkAccessManager()
|
|||||||
|
|
||||||
#endif //QT_NO_BEARERMANAGEMENT
|
#endif //QT_NO_BEARERMANAGEMENT
|
||||||
|
|
||||||
|
|
||||||
|
class QSPFQThread : public QThread
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual void run()
|
||||||
|
{
|
||||||
|
proxies = QNetworkProxyFactory::systemProxyForQuery(query);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
QNetworkProxyQuery query;
|
||||||
|
QList<QNetworkProxy> proxies;
|
||||||
|
};
|
||||||
|
|
||||||
|
//regression test for QTBUG-18799
|
||||||
|
void tst_QNetworkProxyFactory::systemProxyForQueryCalledFromThread()
|
||||||
|
{
|
||||||
|
QUrl url(QLatin1String("http://qt.nokia.com"));
|
||||||
|
QNetworkProxyQuery query(url);
|
||||||
|
QSPFQThread thread;
|
||||||
|
thread.query = query;
|
||||||
|
connect(&thread, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||||
|
thread.start();
|
||||||
|
QTestEventLoop::instance().enterLoop(5);
|
||||||
|
QVERIFY(thread.isFinished());
|
||||||
|
QCOMPARE(thread.proxies, QNetworkProxyFactory::systemProxyForQuery(query));
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QNetworkProxyFactory)
|
QTEST_MAIN(tst_QNetworkProxyFactory)
|
||||||
#include "tst_qnetworkproxyfactory.moc"
|
#include "tst_qnetworkproxyfactory.moc"
|
||||||
|
Loading…
Reference in New Issue
Block a user