forked from qt-creator/qt-creator
Refactor tst_joinAllThreads, to be used in ModelManager d'tor
The idea in this approach is that we only collect those futures,
which have resulted from runAsync. The assumption is that
all tasks associated with those futures may sooner or
later finish, without the need to call qApp->processEvents().
OTOH, we don't collect fake futures coming from Utils::onFinished,
as these requires the spinning event loop in order to deliver
the onFinished signal.
So, the new joinAllThreads() method waits for all collected
futures to finish. We also _do_ want canceled and not finished
futures to finish, since even when they are canceled,
they may still be running and using the internals
of possibly destructed ModelManager. This means, we are only
waiting for other threads to be finished, without reporting
their results to e.g. onFinished() handlers.
Some tests require that all onFinished handlers are also processed.
In order to achieve this, we create a loop inside
tst_joinAllThreads() method and we call joinAllThreads(), so
it will wait for all pending queue to finish, and then we call process
events, in order to let finished futures propagate their results
to their respective onFinished() handlers.
Some handlers may have stared another threads when being processed,
so we may expect that some new futures will appear.
So, after processing the events we check if any new events
appeared, and in this case we repeat the loop.
Otherwise, we finish synchronization.
Amends: 96c860159b
Task-number: QTCREATORBUG-25350
Change-Id: I5e44150c55f6be00445a5695938482d948990c94
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@qt.io>
This commit is contained in:
@@ -106,6 +106,7 @@ ModelManagerInterface::ModelManagerInterface(QObject *parent)
|
|||||||
m_defaultImportPaths(environmentImportPaths()),
|
m_defaultImportPaths(environmentImportPaths()),
|
||||||
m_pluginDumper(new PluginDumper(this))
|
m_pluginDumper(new PluginDumper(this))
|
||||||
{
|
{
|
||||||
|
m_futureSynchronizer.setCancelOnWait(false);
|
||||||
m_indexerDisabled = qEnvironmentVariableIsSet("QTC_NO_CODE_INDEXER");
|
m_indexerDisabled = qEnvironmentVariableIsSet("QTC_NO_CODE_INDEXER");
|
||||||
|
|
||||||
m_updateCppQmlTypesTimer = new QTimer(this);
|
m_updateCppQmlTypesTimer = new QTimer(this);
|
||||||
@@ -307,20 +308,6 @@ void ModelManagerInterface::updateSourceFiles(const QStringList &files,
|
|||||||
refreshSourceFiles(files, emitDocumentOnDiskChanged);
|
refreshSourceFiles(files, emitDocumentOnDiskChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelManagerInterface::cleanupFutures()
|
|
||||||
{
|
|
||||||
QMutexLocker lock(&m_futuresMutex);
|
|
||||||
const int maxFutures = 10;
|
|
||||||
if (m_futures.size() > maxFutures) {
|
|
||||||
const QList<QFuture<void>> futures = m_futures;
|
|
||||||
m_futures.clear();
|
|
||||||
for (const QFuture<void> &future : futures) {
|
|
||||||
if (!(future.isFinished() || future.isCanceled()))
|
|
||||||
m_futures.append(future);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QFuture<void> ModelManagerInterface::refreshSourceFiles(const QStringList &sourceFiles,
|
QFuture<void> ModelManagerInterface::refreshSourceFiles(const QStringList &sourceFiles,
|
||||||
bool emitDocumentOnDiskChanged)
|
bool emitDocumentOnDiskChanged)
|
||||||
{
|
{
|
||||||
@@ -355,9 +342,9 @@ QFuture<void> ModelManagerInterface::refreshSourceFiles(const QStringList &sourc
|
|||||||
|
|
||||||
void ModelManagerInterface::fileChangedOnDisk(const QString &path)
|
void ModelManagerInterface::fileChangedOnDisk(const QString &path)
|
||||||
{
|
{
|
||||||
Utils::runAsync(&ModelManagerInterface::parse,
|
addFuture(Utils::runAsync(&ModelManagerInterface::parse,
|
||||||
workingCopyInternal(), QStringList(path),
|
workingCopyInternal(), QStringList(path),
|
||||||
this, Dialect(Dialect::AnyLanguage), true);
|
this, Dialect(Dialect::AnyLanguage), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelManagerInterface::removeFiles(const QStringList &files)
|
void ModelManagerInterface::removeFiles(const QStringList &files)
|
||||||
@@ -654,7 +641,7 @@ QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::allProjectInfos
|
|||||||
|
|
||||||
bool ModelManagerInterface::isIdle() const
|
bool ModelManagerInterface::isIdle() const
|
||||||
{
|
{
|
||||||
return m_futures.isEmpty();
|
return m_futureSynchronizer.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelManagerInterface::emitDocumentChangedOnDisk(Document::Ptr doc)
|
void ModelManagerInterface::emitDocumentChangedOnDisk(Document::Ptr doc)
|
||||||
@@ -1571,41 +1558,37 @@ void ModelManagerInterface::setDefaultVContext(const ViewerContext &vContext)
|
|||||||
m_defaultVContexts[vContext.language] = vContext;
|
m_defaultVContexts[vContext.language] = vContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModelManagerInterface::joinAllThreads()
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
FutureSynchronizer futureSynchronizer;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_futuresMutex);
|
||||||
|
futureSynchronizer = m_futureSynchronizer;
|
||||||
|
m_futureSynchronizer.clearFutures();
|
||||||
|
}
|
||||||
|
if (futureSynchronizer.isEmpty())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ModelManagerInterface::test_joinAllThreads()
|
void ModelManagerInterface::test_joinAllThreads()
|
||||||
{
|
{
|
||||||
// Loop since futures can spawn more futures as they finish.
|
|
||||||
while (true) {
|
while (true) {
|
||||||
QFuture<void> f;
|
joinAllThreads();
|
||||||
// get one future
|
// In order to process all onFinished handlers of finished futures
|
||||||
{
|
QCoreApplication::processEvents();
|
||||||
QMutexLocker lock(&m_futuresMutex);
|
QMutexLocker lock(&m_futuresMutex);
|
||||||
for (QFuture<void> &future : m_futures) {
|
// If handlers created new futures, repeat the loop
|
||||||
if (!future.isFinished() && !future.isCanceled()) {
|
if (m_futureSynchronizer.isEmpty())
|
||||||
f = future;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!f.isFinished() && !f.isCanceled()) {
|
|
||||||
f.waitForFinished();
|
|
||||||
|
|
||||||
// Some futures trigger more futures from connected signals
|
|
||||||
// and in tests, we care about finishing all of these too.
|
|
||||||
QEventLoop().processEvents();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_futures.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelManagerInterface::addFuture(const QFuture<void> &future)
|
void ModelManagerInterface::addFuture(const QFuture<void> &future)
|
||||||
{
|
{
|
||||||
{
|
|
||||||
QMutexLocker lock(&m_futuresMutex);
|
QMutexLocker lock(&m_futuresMutex);
|
||||||
m_futures.append(future);
|
m_futureSynchronizer.addFuture(future);
|
||||||
}
|
|
||||||
cleanupFutures();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Document::Ptr ModelManagerInterface::ensuredGetDocumentForPath(const QString &filePath)
|
Document::Ptr ModelManagerInterface::ensuredGetDocumentForPath(const QString &filePath)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
#include <cplusplus/CppDocument.h>
|
#include <cplusplus/CppDocument.h>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
|
#include <utils/futuresynchronizer.h>
|
||||||
#include <utils/qrcparser.h>
|
#include <utils/qrcparser.h>
|
||||||
|
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
@@ -243,7 +244,7 @@ protected:
|
|||||||
void setDefaultProject(const ProjectInfo &pInfo, ProjectExplorer::Project *p);
|
void setDefaultProject(const ProjectInfo &pInfo, ProjectExplorer::Project *p);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cleanupFutures();
|
void joinAllThreads();
|
||||||
void iterateQrcFiles(ProjectExplorer::Project *project,
|
void iterateQrcFiles(ProjectExplorer::Project *project,
|
||||||
QrcResourceSelector resources,
|
QrcResourceSelector resources,
|
||||||
const std::function<void(Utils::QrcParser::ConstPtr)> &callback);
|
const std::function<void(Utils::QrcParser::ConstPtr)> &callback);
|
||||||
@@ -280,7 +281,7 @@ private:
|
|||||||
PluginDumper *m_pluginDumper = nullptr;
|
PluginDumper *m_pluginDumper = nullptr;
|
||||||
|
|
||||||
mutable QMutex m_futuresMutex;
|
mutable QMutex m_futuresMutex;
|
||||||
QList<QFuture<void>> m_futures;
|
Utils::FutureSynchronizer m_futureSynchronizer;
|
||||||
|
|
||||||
bool m_indexerDisabled = false;
|
bool m_indexerDisabled = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -300,9 +300,10 @@ void PluginDumper::qmlPluginTypeDumpDone(int exitCode)
|
|||||||
QLatin1String("<dump of ") + libraryPath + QLatin1Char('>'));
|
QLatin1String("<dump of ") + libraryPath + QLatin1Char('>'));
|
||||||
future.reportFinished(&infos);
|
future.reportFinished(&infos);
|
||||||
});
|
});
|
||||||
|
m_modelManager->addFuture(future);
|
||||||
|
|
||||||
auto finalFuture = Utils::onFinished(future, this,
|
Utils::onFinished(future, this, [this, libraryInfo, privatePlugin, libraryPath]
|
||||||
[this, libraryInfo, privatePlugin, libraryPath] (const QFuture<CppQmlTypesInfo>& future) {
|
(const QFuture<CppQmlTypesInfo>& future) {
|
||||||
CppQmlTypesInfo infos = future.result();
|
CppQmlTypesInfo infos = future.result();
|
||||||
|
|
||||||
LibraryInfo libInfo = libraryInfo;
|
LibraryInfo libInfo = libraryInfo;
|
||||||
@@ -325,7 +326,6 @@ void PluginDumper::qmlPluginTypeDumpDone(int exitCode)
|
|||||||
|
|
||||||
m_modelManager->updateLibraryInfo(libraryPath, libInfo);
|
m_modelManager->updateLibraryInfo(libraryPath, libInfo);
|
||||||
});
|
});
|
||||||
m_modelManager->addFuture(finalFuture);
|
|
||||||
} else {
|
} else {
|
||||||
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone);
|
libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone);
|
||||||
libraryInfo.updateFingerprint();
|
libraryInfo.updateFingerprint();
|
||||||
@@ -395,6 +395,7 @@ QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(c
|
|||||||
|
|
||||||
future.reportFinished(&result);
|
future.reportFinished(&result);
|
||||||
});
|
});
|
||||||
|
m_modelManager->addFuture(future);
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
@@ -598,12 +599,12 @@ void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths,
|
|||||||
const QString &libraryPath,
|
const QString &libraryPath,
|
||||||
QmlJS::LibraryInfo libraryInfo)
|
QmlJS::LibraryInfo libraryInfo)
|
||||||
{
|
{
|
||||||
auto future = Utils::onFinished(loadQmlTypeDescription(qmltypesFilePaths), this, [=](const QFuture<PluginDumper::QmlTypeDescription> &typesFuture)
|
Utils::onFinished(loadQmlTypeDescription(qmltypesFilePaths), this, [=](const QFuture<PluginDumper::QmlTypeDescription> &typesFuture)
|
||||||
{
|
{
|
||||||
PluginDumper::QmlTypeDescription typesResult = typesFuture.result();
|
PluginDumper::QmlTypeDescription typesResult = typesFuture.result();
|
||||||
if (!typesResult.dependencies.isEmpty())
|
if (!typesResult.dependencies.isEmpty())
|
||||||
{
|
{
|
||||||
auto depFuture = Utils::onFinished(loadDependencies(typesResult.dependencies, QSharedPointer<QSet<QString>>()), this,
|
Utils::onFinished(loadDependencies(typesResult.dependencies, QSharedPointer<QSet<QString>>()), this,
|
||||||
[typesResult, libraryInfo, libraryPath, this] (const QFuture<PluginDumper::DependencyInfo> &loadFuture)
|
[typesResult, libraryInfo, libraryPath, this] (const QFuture<PluginDumper::DependencyInfo> &loadFuture)
|
||||||
{
|
{
|
||||||
PluginDumper::DependencyInfo loadResult = loadFuture.result();
|
PluginDumper::DependencyInfo loadResult = loadFuture.result();
|
||||||
@@ -621,7 +622,6 @@ void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths,
|
|||||||
typesResult.moduleApis, objects);
|
typesResult.moduleApis, objects);
|
||||||
m_modelManager->updateLibraryInfo(libraryPath, libInfo);
|
m_modelManager->updateLibraryInfo(libraryPath, libInfo);
|
||||||
});
|
});
|
||||||
m_modelManager->addFuture(depFuture);
|
|
||||||
} else {
|
} else {
|
||||||
QmlJS::LibraryInfo libInfo = libraryInfo;
|
QmlJS::LibraryInfo libInfo = libraryInfo;
|
||||||
prepareLibraryInfo(libInfo, libraryPath, typesResult.dependencies,
|
prepareLibraryInfo(libInfo, libraryPath, typesResult.dependencies,
|
||||||
@@ -630,7 +630,6 @@ void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths,
|
|||||||
m_modelManager->updateLibraryInfo(libraryPath, libInfo);
|
m_modelManager->updateLibraryInfo(libraryPath, libInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m_modelManager->addFuture(future);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginDumper::runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info,
|
void PluginDumper::runQmlDump(const QmlJS::ModelManagerInterface::ProjectInfo &info,
|
||||||
|
|||||||
@@ -101,9 +101,7 @@ void tst_Check::initTestCase()
|
|||||||
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
|
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
|
||||||
ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
|
ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
|
||||||
modelManager, false);
|
modelManager, false);
|
||||||
QCoreApplication::processEvents();
|
|
||||||
modelManager->test_joinAllThreads();
|
modelManager->test_joinAllThreads();
|
||||||
QCoreApplication::processEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool offsetComparator(const Message &lhs, const Message &rhs)
|
static bool offsetComparator(const Message &lhs, const Message &rhs)
|
||||||
|
|||||||
@@ -151,9 +151,7 @@ void tst_Dependencies::test()
|
|||||||
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
|
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
|
||||||
ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
|
ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
|
||||||
ModelManagerInterface::instance(), false);
|
ModelManagerInterface::instance(), false);
|
||||||
QCoreApplication::processEvents();
|
|
||||||
ModelManagerInterface::instance()->test_joinAllThreads();
|
ModelManagerInterface::instance()->test_joinAllThreads();
|
||||||
QCoreApplication::processEvents();
|
|
||||||
TestData data = testData(filename);
|
TestData data = testData(filename);
|
||||||
Document::MutablePtr doc = data.doc;
|
Document::MutablePtr doc = data.doc;
|
||||||
int nExpectedSemanticMessages = data.semanticMessages;
|
int nExpectedSemanticMessages = data.semanticMessages;
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ void scanDirectory(const QString &dir)
|
|||||||
paths.maybeInsert(Utils::FilePath::fromString(dir), Dialect::Qml);
|
paths.maybeInsert(Utils::FilePath::fromString(dir), Dialect::Qml);
|
||||||
ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), paths,
|
ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), paths,
|
||||||
ModelManagerInterface::instance(), false);
|
ModelManagerInterface::instance(), false);
|
||||||
QCoreApplication::processEvents();
|
|
||||||
ModelManagerInterface::instance()->test_joinAllThreads();
|
ModelManagerInterface::instance()->test_joinAllThreads();
|
||||||
ViewerContext vCtx;
|
ViewerContext vCtx;
|
||||||
vCtx.paths.append(dir);
|
vCtx.paths.append(dir);
|
||||||
@@ -273,7 +272,6 @@ void tst_ImportCheck::importTypes()
|
|||||||
modelManager->activateScan();
|
modelManager->activateScan();
|
||||||
|
|
||||||
modelManager->updateSourceFiles(QStringList(qmlFile), false);
|
modelManager->updateSourceFiles(QStringList(qmlFile), false);
|
||||||
QCoreApplication::processEvents();
|
|
||||||
modelManager->test_joinAllThreads();
|
modelManager->test_joinAllThreads();
|
||||||
|
|
||||||
Snapshot snapshot = modelManager->newestSnapshot();
|
Snapshot snapshot = modelManager->newestSnapshot();
|
||||||
@@ -288,7 +286,6 @@ void tst_ImportCheck::importTypes()
|
|||||||
return link();
|
return link();
|
||||||
};
|
};
|
||||||
getContext();
|
getContext();
|
||||||
QCoreApplication::processEvents();
|
|
||||||
modelManager->test_joinAllThreads();
|
modelManager->test_joinAllThreads();
|
||||||
snapshot = modelManager->newestSnapshot();
|
snapshot = modelManager->newestSnapshot();
|
||||||
doc = snapshot.document(qmlFile);
|
doc = snapshot.document(qmlFile);
|
||||||
@@ -367,7 +364,6 @@ void tst_ImportCheck::moduleMapping()
|
|||||||
scanDirectory(qtQuickImportPath);
|
scanDirectory(qtQuickImportPath);
|
||||||
|
|
||||||
modelManager->updateSourceFiles(QStringList(qmlFile), false);
|
modelManager->updateSourceFiles(QStringList(qmlFile), false);
|
||||||
QCoreApplication::processEvents();
|
|
||||||
modelManager->test_joinAllThreads();
|
modelManager->test_joinAllThreads();
|
||||||
|
|
||||||
Snapshot snapshot = modelManager->newestSnapshot();
|
Snapshot snapshot = modelManager->newestSnapshot();
|
||||||
@@ -383,7 +379,6 @@ void tst_ImportCheck::moduleMapping()
|
|||||||
return link();
|
return link();
|
||||||
};
|
};
|
||||||
getContext();
|
getContext();
|
||||||
QCoreApplication::processEvents();
|
|
||||||
modelManager->test_joinAllThreads();
|
modelManager->test_joinAllThreads();
|
||||||
snapshot = modelManager->newestSnapshot();
|
snapshot = modelManager->newestSnapshot();
|
||||||
doc = snapshot.document(qmlFile);
|
doc = snapshot.document(qmlFile);
|
||||||
|
|||||||
Reference in New Issue
Block a user