CppTools: Make sure there's only one C++ parsing thread per project

Switching back and forth between targets or build configurations could
result in an arbitrary amount of C++ parser threads running at the same
time, wasting valuable resources.
We now cancel a currently running parser thread when starting a new one
for the same project.

Fixes: QTCREATORBUG-24890
Change-Id: Ie1afc4971515fcad01dae182578fd77daa642cec
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Christian Kandeler
2021-08-19 12:30:11 +02:00
parent db014de91c
commit 303b554543
2 changed files with 63 additions and 50 deletions

View File

@@ -144,17 +144,27 @@ namespace Internal {
static CppModelManager *m_instance;
class ProjectData
{
public:
ProjectInfo::Ptr projectInfo;
QFutureWatcher<void> *indexer = nullptr;
bool fullyIndexed = false;
};
class CppModelManagerPrivate
{
public:
void setupWatcher(const QFuture<void> &future, ProjectExplorer::Project *project,
ProjectData *projectData, CppModelManager *q);
// Snapshot
mutable QMutex m_snapshotMutex;
Snapshot m_snapshot;
// Project integration
mutable QMutex m_projectMutex;
QMap<ProjectExplorer::Project *, ProjectInfo::Ptr> m_projectToProjectsInfo;
QHash<ProjectExplorer::Project *, bool> m_projectToIndexerCanceled;
QHash<ProjectExplorer::Project *, ProjectData> m_projectData;
QMap<Utils::FilePath, QList<ProjectPart::Ptr> > m_fileToProjectParts;
QMap<QString, ProjectPart::Ptr> m_projectPartIdToProjectProjectPart;
// The members below are cached/(re)calculated from the projects and/or their project parts
@@ -747,9 +757,9 @@ void CppModelManager::ensureUpdated()
QStringList CppModelManager::internalProjectFiles() const
{
QStringList files;
for (const ProjectInfo::Ptr &pinfo : qAsConst(d->m_projectToProjectsInfo)) {
foreach (const ProjectPart::Ptr &part, pinfo->projectParts()) {
foreach (const ProjectFile &file, part->files)
for (const ProjectData &projectData : qAsConst(d->m_projectData)) {
for (const ProjectPart::Ptr &part : projectData.projectInfo->projectParts()) {
for (const ProjectFile &file : part->files)
files += file.path;
}
}
@@ -760,9 +770,9 @@ QStringList CppModelManager::internalProjectFiles() const
ProjectExplorer::HeaderPaths CppModelManager::internalHeaderPaths() const
{
ProjectExplorer::HeaderPaths headerPaths;
for (const ProjectInfo::Ptr &pinfo : qAsConst(d->m_projectToProjectsInfo)) {
foreach (const ProjectPart::Ptr &part, pinfo->projectParts()) {
foreach (const ProjectExplorer::HeaderPath &path, part->headerPaths) {
for (const ProjectData &projectData: qAsConst(d->m_projectData)) {
for (const ProjectPart::Ptr &part : projectData.projectInfo->projectParts()) {
for (const ProjectExplorer::HeaderPath &path : part->headerPaths) {
ProjectExplorer::HeaderPath hp(QDir::cleanPath(path.path), path.type);
if (!headerPaths.contains(hp))
headerPaths.push_back(std::move(hp));
@@ -788,8 +798,8 @@ ProjectExplorer::Macros CppModelManager::internalDefinedMacros() const
{
ProjectExplorer::Macros macros;
QSet<ProjectExplorer::Macro> alreadyIn;
for (const ProjectInfo::Ptr &pinfo : qAsConst(d->m_projectToProjectsInfo)) {
for (const ProjectPart::Ptr &part : pinfo->projectParts()) {
for (const ProjectData &projectData : qAsConst(d->m_projectData)) {
for (const ProjectPart::Ptr &part : projectData.projectInfo->projectParts()) {
addUnique(part->toolChainMacros, macros, alreadyIn);
addUnique(part->projectMacros, macros, alreadyIn);
}
@@ -972,13 +982,14 @@ QFuture<void> CppModelManager::updateSourceFiles(const QSet<QString> &sourceFile
QList<ProjectInfo::Ptr> CppModelManager::projectInfos() const
{
QMutexLocker locker(&d->m_projectMutex);
return d->m_projectToProjectsInfo.values();
return Utils::transform<QList<ProjectInfo::Ptr>>(d->m_projectData,
[](const ProjectData &d) { return d.projectInfo; });
}
ProjectInfo::Ptr CppModelManager::projectInfo(ProjectExplorer::Project *project) const
{
QMutexLocker locker(&d->m_projectMutex);
return d->m_projectToProjectsInfo.value(project);
return d->m_projectData.value(project).projectInfo;
}
/// \brief Remove all files and their includes (recursively) of given ProjectInfo from the snapshot.
@@ -1083,38 +1094,35 @@ void CppModelManager::recalculateProjectPartMappings()
{
d->m_projectPartIdToProjectProjectPart.clear();
d->m_fileToProjectParts.clear();
foreach (const ProjectInfo::Ptr &projectInfo, d->m_projectToProjectsInfo) {
foreach (const ProjectPart::Ptr &projectPart, projectInfo->projectParts()) {
for (const ProjectData &projectData : qAsConst(d->m_projectData)) {
for (const ProjectPart::Ptr &projectPart : projectData.projectInfo->projectParts()) {
d->m_projectPartIdToProjectProjectPart[projectPart->id()] = projectPart;
foreach (const ProjectFile &cxxFile, projectPart->files)
for (const ProjectFile &cxxFile : projectPart->files)
d->m_fileToProjectParts[Utils::FilePath::fromString(cxxFile.path)].append(
projectPart);
}
}
d->m_symbolFinder.clearCache();
}
void CppModelManager::watchForCanceledProjectIndexer(const QFuture<void> &future,
ProjectExplorer::Project *project)
void CppModelManagerPrivate::setupWatcher(const QFuture<void> &future,
ProjectExplorer::Project *project,
ProjectData *projectData, CppModelManager *q)
{
if (future.isCanceled() || future.isFinished())
return;
auto watcher = new QFutureWatcher<void>(this);
connect(watcher, &QFutureWatcher<void>::canceled, this, [this, project, watcher]() {
if (d->m_projectToIndexerCanceled.contains(project)) // Project not yet removed
d->m_projectToIndexerCanceled.insert(project, true);
watcher->disconnect(this);
projectData->indexer = new QFutureWatcher<void>(q);
const auto handleFinished = [this, project, watcher = projectData->indexer, q] {
if (const auto it = m_projectData.find(project);
it != m_projectData.end() && it->indexer == watcher) {
it->indexer = nullptr;
it->fullyIndexed = !watcher->isCanceled();
}
watcher->disconnect(q);
watcher->deleteLater();
});
connect(watcher, &QFutureWatcher<void>::finished, this, [this, project, watcher]() {
d->m_projectToIndexerCanceled.remove(project);
watcher->disconnect(this);
watcher->deleteLater();
});
watcher->setFuture(future);
};
q->connect(projectData->indexer, &QFutureWatcher<void>::canceled, q, handleFinished);
q->connect(projectData->indexer, &QFutureWatcher<void>::finished, q, handleFinished);
projectData->indexer->setFuture(future);
}
void CppModelManager::updateCppEditorDocuments(bool projectsUpdated) const
@@ -1160,23 +1168,23 @@ QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo::Ptr &newProj
if (!project)
return {};
ProjectData *projectData = nullptr;
{ // Only hold the mutex for a limited scope, so the dumping afterwards does not deadlock.
QMutexLocker projectLocker(&d->m_projectMutex);
const QSet<QString> newSourceFiles = newProjectInfo->sourceFiles();
// Check if we can avoid a full reindexing
const ProjectInfo::Ptr oldProjectInfo = d->m_projectToProjectsInfo.value(project);
const bool previousIndexerCanceled = d->m_projectToIndexerCanceled.value(project, false);
if (!previousIndexerCanceled && oldProjectInfo) {
ProjectInfoComparer comparer(*oldProjectInfo, *newProjectInfo);
const auto it = d->m_projectData.find(project);
if (it != d->m_projectData.end() && it->projectInfo && it->fullyIndexed) {
ProjectInfoComparer comparer(*it->projectInfo, *newProjectInfo);
if (comparer.configurationOrFilesChanged()) {
d->m_dirty = true;
// If the project configuration changed, do a full reindexing
if (comparer.configurationChanged()) {
removeProjectInfoFilesAndIncludesFromSnapshot(*oldProjectInfo);
removeProjectInfoFilesAndIncludesFromSnapshot(*it->projectInfo);
filesToReindex.unite(newSourceFiles);
// The "configuration file" includes all defines and therefore should be updated
@@ -1212,9 +1220,16 @@ QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo::Ptr &newProj
}
// Update Project/ProjectInfo and File/ProjectPart table
d->m_projectToProjectsInfo.insert(project, newProjectInfo);
if (it != d->m_projectData.end()) {
if (it->indexer)
it->indexer->cancel();
it->projectInfo = newProjectInfo;
it->fullyIndexed = false;
}
projectData = it == d->m_projectData.end()
? &(d->m_projectData[project] = ProjectData{newProjectInfo, nullptr, false})
: &(*it);
recalculateProjectPartMappings();
} // Mutex scope
// If requested, dump everything we got
@@ -1242,10 +1257,12 @@ QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo::Ptr &newProj
// Trigger reindexing
const QFuture<void> indexingFuture = updateSourceFiles(filesToReindex,
ForcedProgressNotification);
if (!filesToReindex.isEmpty()) {
d->m_projectToIndexerCanceled.insert(project, false);
}
watchForCanceledProjectIndexer(indexingFuture, project);
// It's safe to do this here, as only the UI thread writes to the map and no other thread
// uses the indexer value.
// FIXME: Use a read/write lock instead of a mutex.
d->setupWatcher(indexingFuture, project, projectData, this);
return indexingFuture;
}
@@ -1336,14 +1353,12 @@ void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project)
{
QStringList idsOfRemovedProjectParts;
d->m_projectToIndexerCanceled.remove(project);
{
QMutexLocker locker(&d->m_projectMutex);
d->m_dirty = true;
const QStringList projectPartsIdsBefore = d->m_projectPartIdToProjectProjectPart.keys();
d->m_projectToProjectsInfo.remove(project);
d->m_projectData.remove(project);
recalculateProjectPartMappings();
const QStringList projectPartsIdsAfter = d->m_projectPartIdToProjectProjectPart.keys();
@@ -1363,7 +1378,7 @@ void CppModelManager::onActiveProjectChanged(ProjectExplorer::Project *project)
{
QMutexLocker locker(&d->m_projectMutex);
if (!d->m_projectToProjectsInfo.contains(project))
if (!d->m_projectData.contains(project))
return; // Not yet known to us.
}

View File

@@ -286,8 +286,6 @@ private:
void initializeBuiltinModelManagerSupport();
void delayedGC();
void recalculateProjectPartMappings();
void watchForCanceledProjectIndexer(const QFuture<void> &future,
ProjectExplorer::Project *project);
void replaceSnapshot(const CPlusPlus::Snapshot &newSnapshot);
void removeFilesFromSnapshot(const QSet<QString> &removedFiles);