File indexing completed on 2024-05-12 04:37:58

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "duchain.h"
0009 #include "duchainlock.h"
0010 
0011 #include <QCoreApplication>
0012 #include <QHash>
0013 #include <QMultiMap>
0014 #include <QProcessEnvironment>
0015 #include <QReadWriteLock>
0016 #include <QAtomicInt>
0017 #include <QThread>
0018 #include <QStandardPaths>
0019 #include <QMutex>
0020 #include <QMutexLocker>
0021 #include <QRecursiveMutex>
0022 #include <QTimer>
0023 #include <QRandomGenerator>
0024 
0025 #include <interfaces/idocumentcontroller.h>
0026 #include <interfaces/icore.h>
0027 #include <interfaces/ilanguagecontroller.h>
0028 #include <interfaces/isession.h>
0029 
0030 #include "../interfaces/ilanguagesupport.h"
0031 #include "../interfaces/icodehighlighting.h"
0032 #include "../backgroundparser/backgroundparser.h"
0033 #include <debug.h>
0034 
0035 #include "language-features.h"
0036 #include "topducontext.h"
0037 #include "topducontextdata.h"
0038 #include "topducontextdynamicdata.h"
0039 #include "parsingenvironment.h"
0040 #include "declaration.h"
0041 #include "definitions.h"
0042 #include "duchainutils.h"
0043 #include "use.h"
0044 #include "uses.h"
0045 #include "abstractfunctiondeclaration.h"
0046 #include "duchainregister.h"
0047 #include "persistentsymboltable.h"
0048 #include "serialization/itemrepository.h"
0049 #include "waitforupdate.h"
0050 #include "importers.h"
0051 #include "codemodel.h"
0052 
0053 #if HAVE_MALLOC_TRIM
0054 #include "malloc.h"
0055 #endif
0056 
0057 namespace {
0058 //Additional "soft" cleanup steps that are done before the actual cleanup.
0059 //During "soft" cleanup, the consistency is not guaranteed. The repository is
0060 //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared.
0061 //The big advantage of the soft cleanup steps is, that the duchain is always only locked for
0062 //short times, which leads to no lockup in the UI.
0063 const int SOFT_CLEANUP_STEPS = 1;
0064 
0065 // seconds to wait before trying to cleanup the DUChain
0066 const uint cleanupEverySeconds = 200;
0067 
0068 ///Approximate maximum count of top-contexts that are checked during final cleanup
0069 const uint maxFinalCleanupCheckContexts = 2000;
0070 const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup
0071 }
0072 
0073 namespace KDevelop {
0074 /// Set to true as soon as the duchain is deleted
0075 bool DUChain::m_deleted = false;
0076 
0077 ///Must be locked through KDevelop::SpinLock before using chainsByIndex
0078 ///This lock should be locked only for very short times
0079 QMutex DUChain::chainsByIndexLock;
0080 std::vector<TopDUContext*> DUChain::chainsByIndex;
0081 
0082 //This thing is not actually used, but it's needed for compiling
0083 DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint)
0084 
0085 //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored.
0086 class EnvironmentInformationItem
0087 {
0088 public:
0089     EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext)
0090         , m_size(size)
0091     {
0092     }
0093 
0094     ~EnvironmentInformationItem()
0095     {
0096     }
0097 
0098     Q_DISABLE_COPY_MOVE(EnvironmentInformationItem)
0099 
0100     unsigned int hash() const
0101     {
0102         return m_topContext;
0103     }
0104 
0105     unsigned int itemSize() const
0106     {
0107         return sizeof(*this) + m_size;
0108     }
0109 
0110     uint m_topContext;
0111     uint m_size;//Size of the data behind, that holds the actual item
0112 };
0113 
0114 struct ItemRepositoryIndexHash
0115 {
0116     uint
0117     operator()(unsigned int __x) const
0118     { return 173 * (__x >> 2) + 11 * (__x >> 16); }
0119 };
0120 
0121 class EnvironmentInformationRequest
0122 {
0123 public:
0124 
0125     ///This constructor should only be used for lookup
0126     EnvironmentInformationRequest(uint topContextIndex) : m_file(nullptr)
0127         , m_index(topContextIndex)
0128     {
0129     }
0130 
0131     EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file)
0132         , m_index(file->indexedTopContext().index())
0133     {
0134     }
0135 
0136     enum {
0137         AverageSize = 32 //This should be the approximate average size of an Item
0138     };
0139 
0140     unsigned int hash() const
0141     {
0142         return m_index;
0143     }
0144 
0145     uint itemSize() const
0146     {
0147         return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func());
0148     }
0149 
0150     void createItem(EnvironmentInformationItem* item) const
0151     {
0152         new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func()));
0153         Q_ASSERT(m_file->d_func()->m_dynamic);
0154         auto* data =
0155             reinterpret_cast<DUChainBaseData*>(reinterpret_cast<char*>(item) + sizeof(EnvironmentInformationItem));
0156         DUChainItemSystem::self().copy(*m_file->d_func(), *data, true);
0157         Q_ASSERT(data->m_range == m_file->d_func()->m_range);
0158         Q_ASSERT(data->classId == m_file->d_func()->classId);
0159         Q_ASSERT(data->m_dynamic == false);
0160     }
0161 
0162     static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&)
0163     {
0164         item->~EnvironmentInformationItem();
0165         //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic()
0166         //We just need to make sure that every environment-file is dynamic when it's deleted
0167 //     DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem)));
0168     }
0169 
0170     static bool persistent(const EnvironmentInformationItem*)
0171     {
0172         //Cleanup done separately
0173         return true;
0174     }
0175 
0176     bool equals(const EnvironmentInformationItem* item) const
0177     {
0178         return m_index == item->m_topContext;
0179     }
0180 
0181     const ParsingEnvironmentFile* m_file;
0182     uint m_index;
0183 };
0184 
0185 ///A list of environment-information/top-contexts mapped to a file-name
0186 class EnvironmentInformationListItem
0187 {
0188 public:
0189     EnvironmentInformationListItem()
0190     {
0191         initializeAppendedLists(true);
0192     }
0193 
0194     explicit EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic)
0195     {
0196         initializeAppendedLists(dynamic);
0197         m_file = rhs.m_file;
0198         copyListsFrom(rhs);
0199     }
0200 
0201     ~EnvironmentInformationListItem()
0202     {
0203         freeAppendedLists();
0204     }
0205 
0206     Q_DISABLE_COPY_MOVE(EnvironmentInformationListItem)
0207 
0208     unsigned int hash() const
0209     {
0210         //We only compare the declaration. This allows us implementing a map, although the item-repository
0211         //originally represents a set.
0212         return m_file.hash();
0213     }
0214 
0215     unsigned short int itemSize() const
0216     {
0217         return dynamicSize();
0218     }
0219 
0220     IndexedString m_file;
0221 
0222     uint classSize() const
0223     {
0224         return sizeof(*this);
0225     }
0226 
0227     START_APPENDED_LISTS(EnvironmentInformationListItem);
0228     ///Contains the index of each contained environment-item
0229     APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items);
0230     END_APPENDED_LISTS(EnvironmentInformationListItem, items);
0231 };
0232 
0233 class EnvironmentInformationListRequest
0234 {
0235 public:
0236 
0237     ///This constructor should only be used for lookup
0238     EnvironmentInformationListRequest(const IndexedString& file) : m_file(file)
0239         , m_item(nullptr)
0240     {
0241     }
0242     ///This is used to actually construct the information in the repository
0243     EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(
0244             file)
0245         , m_item(&item)
0246     {
0247     }
0248 
0249     enum {
0250         AverageSize = 160 //This should be the approximate average size of an Item
0251     };
0252 
0253     unsigned int hash() const
0254     {
0255         return m_file.hash();
0256     }
0257 
0258     uint itemSize() const
0259     {
0260         return m_item->itemSize();
0261     }
0262 
0263     void createItem(EnvironmentInformationListItem* item) const
0264     {
0265         Q_ASSERT(m_item->m_file == m_file);
0266         new (item) EnvironmentInformationListItem(*m_item, false);
0267     }
0268 
0269     static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&)
0270     {
0271         item->~EnvironmentInformationListItem();
0272     }
0273 
0274     static bool persistent(const EnvironmentInformationListItem*)
0275     {
0276         //Cleanup is done separately
0277         return true;
0278     }
0279 
0280     bool equals(const EnvironmentInformationListItem* item) const
0281     {
0282         return m_file == item->m_file;
0283     }
0284 
0285     IndexedString m_file;
0286     const EnvironmentInformationListItem* m_item;
0287 };
0288 
0289 /// NOTE: The following two repositories are thread-safe, and DUChainPrivate::m_chainsMutex should not be locked when
0290 /// using them, because they may trigger I/O.
0291 
0292 /// Maps filenames to a list of top-contexts/environment-information.
0293 using EnvironmentInformationListRepo
0294     = ItemRepository<EnvironmentInformationListItem, EnvironmentInformationListRequest>;
0295 
0296 struct EnvironmentInformationList {
0297 };
0298 template<>
0299 class ItemRepositoryFor<EnvironmentInformationList>
0300 {
0301     friend struct LockedItemRepository;
0302     static EnvironmentInformationListRepo& repo()
0303     {
0304         static QMutex mutex;
0305         static EnvironmentInformationListRepo repo(QStringLiteral("Environment Lists"), &mutex);
0306         return repo;
0307     }
0308 };
0309 
0310 /// Maps top-context-indices to environment-information item.
0311 using EnvironmentInformationRepo = ItemRepository<EnvironmentInformationItem, EnvironmentInformationRequest>;
0312 
0313 struct EnvironmentInformation {
0314 };
0315 template<>
0316 class ItemRepositoryFor<EnvironmentInformation>
0317 {
0318     friend struct LockedItemRepository;
0319     static EnvironmentInformationRepo& repo()
0320     {
0321         static QMutex mutex;
0322         static EnvironmentInformationRepo repo(QStringLiteral("Environment Information"), &mutex);
0323         return repo;
0324     }
0325 };
0326 
0327 class DUChainPrivate;
0328 class DUChainPrivate
0329 {
0330     class CleanupThread
0331         : public QThread
0332     {
0333 public:
0334         explicit CleanupThread(DUChainPrivate* data)
0335             : m_data(data)
0336         {
0337         }
0338 
0339         void stopThread()
0340         {
0341             quit();
0342             wait();
0343         }
0344 
0345 private:
0346         void run() override
0347         {
0348             QTimer timer;
0349             connect(&timer, &QTimer::timeout, &timer, [this]() {
0350                     Q_ASSERT(QThread::currentThread() == this);
0351                     //Just to make sure the cache is cleared periodically
0352                     ModificationRevisionSet::clearCache();
0353 
0354                     m_data->doMoreCleanup(SOFT_CLEANUP_STEPS, TryLock);
0355                 });
0356             timer.start(cleanupEverySeconds * 1000);
0357             exec();
0358         }
0359         DUChainPrivate* m_data;
0360     };
0361 
0362 public:
0363     DUChainPrivate()
0364         : instance(nullptr)
0365         , m_cleanupDisabled(false)
0366         , m_destroyed(false)
0367     {
0368 #if defined(TEST_NO_CLEANUP)
0369         m_cleanupDisabled = true;
0370 #endif
0371 
0372         qRegisterMetaType<DUChainBasePointer>("KDevelop::DUChainBasePointer");
0373         qRegisterMetaType<DUContextPointer>("KDevelop::DUContextPointer");
0374         qRegisterMetaType<TopDUContextPointer>("KDevelop::TopDUContextPointer");
0375         qRegisterMetaType<DeclarationPointer>("KDevelop::DeclarationPointer");
0376         qRegisterMetaType<FunctionDeclarationPointer>("KDevelop::FunctionDeclarationPointer");
0377         qRegisterMetaType<KDevelop::IndexedString>("KDevelop::IndexedString");
0378         qRegisterMetaType<KDevelop::IndexedTopDUContext>("KDevelop::IndexedTopDUContext");
0379         qRegisterMetaType<KDevelop::ReferencedTopDUContext>("KDevelop::ReferencedTopDUContext");
0380 
0381         instance = new DUChain();
0382         m_cleanup = new CleanupThread(this);
0383         m_cleanup->start();
0384 
0385         DUChain::m_deleted = false;
0386 
0387         ///Loading of some static data:
0388         {
0389             ///@todo Solve this more duchain-like
0390             QFile f(globalItemRepositoryRegistry().path() + QLatin1String("/parsing_environment_data"));
0391             bool opened = f.open(QIODevice::ReadOnly);
0392             ///FIXME: ugh, so ugly
0393             ParsingEnvironmentFile::m_staticData =
0394                 reinterpret_cast<StaticParsingEnvironmentData*>(new char[sizeof(StaticParsingEnvironmentData)]);
0395             if (opened) {
0396                 qCDebug(LANGUAGE) << "reading parsing-environment static data";
0397                 //Read
0398                 f.read(reinterpret_cast<char*>(ParsingEnvironmentFile::m_staticData), sizeof(StaticParsingEnvironmentData));
0399             } else {
0400                 qCDebug(LANGUAGE) << "creating new parsing-environment static data";
0401                 //Initialize
0402                 new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData();
0403             }
0404         }
0405 
0406         ///Read in the list of available top-context indices
0407         {
0408             QFile f(globalItemRepositoryRegistry().path() + QLatin1String("/available_top_context_indices"));
0409             bool opened = f.open(QIODevice::ReadOnly);
0410             if (opened) {
0411                 Q_ASSERT((f.size() % sizeof(uint)) == 0);
0412                 m_availableTopContextIndices.resize(f.size() / ( int )sizeof(uint));
0413                 f.read(reinterpret_cast<char*>(m_availableTopContextIndices.data()), f.size());
0414             }
0415         }
0416     }
0417     ~DUChainPrivate()
0418     {
0419         qCDebug(LANGUAGE) << "Destroying";
0420         DUChain::m_deleted = true;
0421         m_cleanup->stopThread();
0422         delete m_cleanup;
0423         delete instance;
0424     }
0425 
0426     void clear()
0427     {
0428         if (!m_cleanupDisabled)
0429             doMoreCleanup();
0430 
0431         DUChainWriteLocker writeLock(DUChain::lock());
0432 
0433         QMutexLocker l(&m_chainsMutex);
0434 
0435         const auto currentChainsByUrl = m_chainsByUrl;
0436         for (TopDUContext* top : currentChainsByUrl) {
0437             removeDocumentChainFromMemory(top);
0438         }
0439 
0440         m_indexEnvironmentInformations.clear();
0441         m_fileEnvironmentInformations.clear();
0442 
0443         Q_ASSERT(m_fileEnvironmentInformations.isEmpty());
0444         Q_ASSERT(m_chainsByUrl.isEmpty());
0445     }
0446 
0447     ///DUChain must be write-locked
0448     ///Also removes from the environment-manager if the top-context is not on disk
0449     void removeDocumentChainFromMemory(TopDUContext* context)
0450     {
0451         QMutexLocker l(&m_chainsMutex);
0452 
0453         {
0454             QMutexLocker l(&m_referenceCountsMutex);
0455 
0456             auto countIt = m_referenceCounts.constFind(context);
0457             if (countIt != m_referenceCounts.constEnd()) {
0458                 //This happens during shutdown, since everything is unloaded
0459                 qCDebug(LANGUAGE) << "removed a top-context that was reference-counted:" << context->url().str() <<
0460                     context->ownIndex();
0461                 m_referenceCounts.erase(countIt);
0462             }
0463         }
0464 
0465         uint index = context->ownIndex();
0466 
0467         // qCDebug(LANGUAGE) << "duchain: removing document" << context->url().str();
0468         Q_ASSERT(hasChainForIndex(index));
0469         Q_ASSERT(m_chainsByUrl.contains(context->url(), context));
0470 
0471         m_chainsByUrl.remove(context->url(), context);
0472 
0473         if (!context->isOnDisk())
0474             instance->removeFromEnvironmentManager(context);
0475 
0476         l.unlock();
0477         //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it
0478         context->deleteSelf();
0479         l.relock();
0480 
0481         Q_ASSERT(hasChainForIndex(index));
0482 
0483         QMutexLocker lock(&DUChain::chainsByIndexLock);
0484         DUChain::chainsByIndex[index] = nullptr;
0485     }
0486 
0487     ///Must be locked before accessing content of this class.
0488     ///Should be released during expensive disk-operations and such.
0489     QRecursiveMutex m_chainsMutex;
0490 
0491     QRecursiveMutex m_cleanupMutex;
0492 
0493     CleanupThread* m_cleanup;
0494 
0495     DUChain* instance;
0496     DUChainLock lock;
0497     QMultiMap<IndexedString, TopDUContext*> m_chainsByUrl;
0498 
0499     //Must be locked before accessing m_referenceCounts
0500     QMutex m_referenceCountsMutex;
0501     QHash<TopDUContext*, uint> m_referenceCounts;
0502 
0503     Definitions m_definitions;
0504     Uses m_uses;
0505     QSet<uint> m_loading;
0506     bool m_cleanupDisabled;
0507 
0508     //List of available top-context indices, protected by m_chainsMutex
0509     QVector<uint> m_availableTopContextIndices;
0510 
0511     ///Used to keep alive the top-context that belong to documents loaded in the editor
0512     QSet<ReferencedTopDUContext> m_openDocumentContexts;
0513 
0514     bool m_destroyed;
0515 
0516     ///The item must not be stored yet
0517     ///m_chainsMutex should not be locked, since this can trigger I/O
0518     void addEnvironmentInformation(ParsingEnvironmentFilePointer info)
0519     {
0520         Q_ASSERT(!findInformation(info->indexedTopContext().index()));
0521         Q_ASSERT(LockedItemRepository::read<EnvironmentInformation>([&](const EnvironmentInformationRepo& repo) {
0522             return repo.findIndex(info->indexedTopContext().index()) == 0;
0523         }));
0524 
0525         QMutexLocker lock(&m_chainsMutex);
0526         m_fileEnvironmentInformations.insert(info->url(), info);
0527 
0528         m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info);
0529 
0530         Q_ASSERT(info->d_func()->classId);
0531     }
0532 
0533     ///The item must be managed currently
0534     ///m_chainsMutex does not need to be locked
0535     void removeEnvironmentInformation(ParsingEnvironmentFilePointer info)
0536     {
0537         info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor
0538 
0539         const auto infoIndex = info->indexedTopContext().index();
0540 
0541         bool removed = false;
0542         bool removed2 = false;
0543         {
0544             QMutexLocker lock(&m_chainsMutex);
0545             removed = m_fileEnvironmentInformations.remove(info->url(), info);
0546             removed2 = m_indexEnvironmentInformations.remove(infoIndex);
0547         }
0548 
0549         LockedItemRepository::write<EnvironmentInformationList>(
0550             [infoIndex,
0551              request = EnvironmentInformationListRequest(info->url())](EnvironmentInformationListRepo& repo) mutable {
0552                 // Remove it from the environment information lists if it was there
0553                 const uint index = repo.findIndex(request);
0554 
0555                 if (index) {
0556                     EnvironmentInformationListItem item(*repo.itemFromIndex(index), true);
0557                     if (item.itemsList().removeOne(infoIndex)) {
0558                         repo.deleteItem(index);
0559                         if (!item.itemsList().empty()) {
0560                             request.m_item = &item;
0561                             repo.index(request);
0562                         }
0563                     }
0564                 }
0565             });
0566 
0567         LockedItemRepository::write<EnvironmentInformation>(
0568             [infoIndex, removed, removed2](EnvironmentInformationRepo& repo) {
0569                 const uint index = repo.findIndex(infoIndex);
0570                 if (index) {
0571                     repo.deleteItem(index);
0572                 }
0573 
0574                 Q_UNUSED(removed);
0575                 Q_UNUSED(removed2);
0576                 Q_ASSERT(index || (removed && removed2));
0577             });
0578 
0579         Q_ASSERT(!findInformation(infoIndex));
0580     }
0581 
0582     ///m_chainsMutex should _not_ be locked, because this may trigger I/O
0583     QList<ParsingEnvironmentFilePointer> getEnvironmentInformation(const IndexedString& url)
0584     {
0585         QList<ParsingEnvironmentFilePointer> ret;
0586 
0587         {
0588             KDevVarLengthArray<uint> topContextIndices;
0589             // First store all the possible indices into the KDevVarLengthArray, so we can process them without holding
0590             // a mutex locked
0591             LockedItemRepository::read<EnvironmentInformationList>(
0592                 [&topContextIndices,
0593                  request = EnvironmentInformationListRequest(url)](const EnvironmentInformationListRepo& repo) {
0594                     const EnvironmentInformationListItem* item = repo.findItem(request);
0595                     if (item) {
0596                         FOREACH_FUNCTION(uint topContextIndex, item->items)
0597                         topContextIndices << topContextIndex;
0598                     }
0599                 });
0600 
0601             // Process the indices in a separate step after copying them from the array, so we don't need
0602             // m_environmentListInfoMutex locked, and can call loadInformation(..) safely, which else might lead to a
0603             // deadlock.
0604             for (uint topContextIndex : qAsConst(topContextIndices)) {
0605                 QExplicitlySharedDataPointer<ParsingEnvironmentFile> p =
0606                     ParsingEnvironmentFilePointer(loadInformation(topContextIndex));
0607                 if (p) {
0608                     ret << p;
0609                 } else {
0610                     qCDebug(LANGUAGE) << "Failed to load environment-information for" <<
0611                         TopDUContextDynamicData::loadUrl(topContextIndex).str();
0612                 }
0613             }
0614         }
0615 
0616         QMutexLocker l(&m_chainsMutex);
0617 
0618         //Add those information that have not been added to the stored lists yet
0619         const auto files = m_fileEnvironmentInformations.values(url);
0620         for (const ParsingEnvironmentFilePointer& file : files) {
0621             if (!ret.contains(file))
0622                 ret << file;
0623         }
0624 
0625         return ret;
0626     }
0627 
0628     ///Must be called _without_ the chainsByIndex spin-lock locked
0629     static inline bool hasChainForIndex(uint index)
0630     {
0631         QMutexLocker lock(&DUChain::chainsByIndexLock);
0632         return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index];
0633     }
0634 
0635     ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded.
0636     static inline TopDUContext* readChainForIndex(uint index)
0637     {
0638         QMutexLocker lock(&DUChain::chainsByIndexLock);
0639         if (DUChain::chainsByIndex.size() > index)
0640             return DUChain::chainsByIndex[index];
0641         else
0642             return nullptr;
0643     }
0644 
0645     ///Makes sure that the chain with the given index is loaded
0646     ///@warning m_chainsMutex must NOT be locked when this is called
0647     void loadChain(uint index, QSet<uint>& loaded)
0648     {
0649         QMutexLocker l(&m_chainsMutex);
0650 
0651         if (!hasChainForIndex(index)) {
0652             if (m_loading.contains(index)) {
0653                 //It's probably being loaded by another thread. So wait until the load is ready
0654                 while (m_loading.contains(index)) {
0655                     l.unlock();
0656                     qCDebug(LANGUAGE) << "waiting for another thread to load index" << index;
0657                     QThread::usleep(50000);
0658                     l.relock();
0659                 }
0660                 loaded.insert(index);
0661                 return;
0662             }
0663             m_loading.insert(index);
0664             loaded.insert(index);
0665 
0666             l.unlock();
0667             qCDebug(LANGUAGE) << "loading top-context" << index;
0668             TopDUContext* chain = TopDUContextDynamicData::load(index);
0669             if (chain) {
0670                 chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex()));
0671 
0672                 if (!chain->usingImportsCache()) {
0673                     //Eventually also load all the imported chains, so the import-structure is built
0674                     const auto importedParentContexts = chain->DUContext::importedParentContexts();
0675                     for (const DUContext::Import& import : importedParentContexts) {
0676                         if (!loaded.contains(import.topContextIndex())) {
0677                             loadChain(import.topContextIndex(), loaded);
0678                         }
0679                     }
0680                 }
0681                 chain->rebuildDynamicImportStructure();
0682 
0683                 chain->setInDuChain(true);
0684                 instance->addDocumentChain(chain);
0685             }
0686 
0687             l.relock();
0688             m_loading.remove(index);
0689         }
0690     }
0691 
0692     ///Stores all environment-information
0693     ///Also makes sure that all information that stays is referenced, so it stays alive.
0694     ///@param atomic If this is false, the write-lock will be released time by time
0695     void storeAllInformation(bool atomic, DUChainWriteLocker& locker)
0696     {
0697         uint cnt = 0;
0698 
0699         QList<IndexedString> urls;
0700         {
0701             QMutexLocker lock(&m_chainsMutex);
0702             urls += m_fileEnvironmentInformations.keys();
0703         }
0704 
0705         for (const IndexedString& url : qAsConst(urls)) {
0706             QList<ParsingEnvironmentFilePointer> check;
0707             {
0708                 QMutexLocker lock(&m_chainsMutex);
0709                 check = m_fileEnvironmentInformations.values(url);
0710             }
0711 
0712             for (const ParsingEnvironmentFilePointer& file : qAsConst(check)) {
0713                 EnvironmentInformationRequest req(file.data());
0714 
0715                 LockedItemRepository::write<EnvironmentInformation>([&](EnvironmentInformationRepo& repo) {
0716                     uint index = repo.findIndex(req);
0717 
0718                     if (file->d_func()->isDynamic()) {
0719                         // This item has been changed, or isn't in the repository yet
0720 
0721                         // Eventually remove an old entry
0722                         if (index)
0723                             repo.deleteItem(index);
0724 
0725                         // Add the new entry to the item repository
0726                         index = repo.index(req);
0727                         Q_ASSERT(index);
0728 
0729                         auto* item = const_cast<EnvironmentInformationItem*>(repo.itemFromIndex(index));
0730                         auto* theData = reinterpret_cast<DUChainBaseData*>(reinterpret_cast<char*>(item)
0731                                                                            + sizeof(EnvironmentInformationItem));
0732 
0733                         Q_ASSERT(theData->m_range == file->d_func()->m_range);
0734                         Q_ASSERT(theData->m_dynamic == false);
0735                         Q_ASSERT(theData->classId == file->d_func()->classId);
0736 
0737                         file->setData(theData);
0738 
0739                         ++cnt;
0740                     } else {
0741                         repo.itemFromIndex(index); // Prevent unloading of the data, by accessing the item
0742                     }
0743                 });
0744             }
0745 
0746             ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an
0747             ///information, and will get crashes.
0748             if (!atomic && (cnt % 100 == 0)) {
0749                 //Release the lock on a regular basis
0750                 locker.unlock();
0751                 locker.lock();
0752             }
0753 
0754             storeInformationList(url);
0755 
0756             //Access the data in the repository, so the bucket isn't unloaded
0757             const auto foundItem = LockedItemRepository::read<EnvironmentInformationList>(
0758                 [request = EnvironmentInformationListRequest(url)](const EnvironmentInformationListRepo& repo) {
0759                     return static_cast<bool>(repo.findItem(request));
0760                 });
0761             if (!foundItem) {
0762                 QMutexLocker chainLock(&m_chainsMutex);
0763                 qCDebug(LANGUAGE) << "Did not find stored item for" << url.str()
0764                                   << "count:" << m_fileEnvironmentInformations.values(url);
0765             }
0766 
0767             if (!atomic) {
0768                 locker.unlock();
0769                 locker.lock();
0770             }
0771         }
0772     }
0773 
0774     QRecursiveMutex& cleanupMutex() { return m_cleanupMutex; }
0775 
0776     /// defines how we interact with the ongoing language parse jobs
0777     enum LockFlag {
0778         /// no locking required, only used when we locked previously
0779         NoLock = 0,
0780         /// lock all parse jobs and block until we succeeded. required at shutdown
0781         BlockingLock = 1,
0782         /// only try to lock and abort on failure, good for the intermittent cleanups
0783         TryLock = 2,
0784     };
0785     ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles
0786     ///doing the cleanup without permanently locking the du-chain. During these steps the consistency
0787     ///of the disk-storage is not guaranteed, but only few changes will be done during these steps,
0788     ///so the final step where the duchain is permanently locked is much faster.
0789     void doMoreCleanup(int retries = 0, LockFlag lockFlag = BlockingLock)
0790     {
0791         if (m_cleanupDisabled)
0792             return;
0793 
0794         //This mutex makes sure that there's never 2 threads at he same time trying to clean up
0795         QMutexLocker lockCleanupMutex(&cleanupMutex());
0796 
0797         if (m_destroyed || m_cleanupDisabled)
0798             return;
0799 
0800         Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock());
0801         DUChainWriteLocker writeLock(instance->lock());
0802 
0803         //This is used to stop all parsing before starting to do the cleanup. This way less happens during the
0804         //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written.
0805         QList<QReadWriteLock*> locked;
0806 
0807         if (lockFlag != NoLock) {
0808             QList<ILanguageSupport*> languages;
0809             if (ICore* core = ICore::self())
0810                 if (ILanguageController* lc = core->languageController())
0811                     languages = lc->loadedLanguages();
0812 
0813             writeLock.unlock();
0814 
0815             //Here we wait for all parsing-threads to stop their processing
0816             for (const auto language : qAsConst(languages)) {
0817                 if (lockFlag == TryLock) {
0818                     if (!language->parseLock()->tryLockForWrite()) {
0819                         qCDebug(LANGUAGE) << "Aborting cleanup because language plugin is still parsing:" <<
0820                             language->name();
0821                         // some language is still parsing, don't interfere with the cleanup
0822                         for (auto* lock : qAsConst(locked)) {
0823                             lock->unlock();
0824                         }
0825 
0826                         return;
0827                     }
0828                 } else {
0829                     language->parseLock()->lockForWrite();
0830                 }
0831                 locked << language->parseLock();
0832             }
0833 
0834             writeLock.lock();
0835 
0836             globalItemRepositoryRegistry().lockForWriting();
0837             qCDebug(LANGUAGE) << "starting cleanup";
0838         }
0839 
0840         QTime startTime = QTime::currentTime();
0841 
0842         storeAllInformation(!retries, writeLock); //Puts environment-information into a repository
0843 
0844         //We don't need to increase the reference-count, since the cleanup-mutex is locked
0845         QSet<TopDUContext*> workOnContexts;
0846 
0847         {
0848             QMutexLocker l(&m_chainsMutex);
0849 
0850             workOnContexts.reserve(m_chainsByUrl.size());
0851             for (TopDUContext* top : qAsConst(m_chainsByUrl)) {
0852                 workOnContexts << top;
0853                 Q_ASSERT(hasChainForIndex(top->ownIndex()));
0854             }
0855         }
0856 
0857         for (TopDUContext* context : qAsConst(workOnContexts)) {
0858             context->m_dynamicData->store();
0859 
0860             if (retries) {
0861                 //Eventually give other threads a chance to access the duchain
0862                 writeLock.unlock();
0863                 //Sleep to give the other threads a realistic chance to get a read-lock in between
0864                 QThread::usleep(500);
0865                 writeLock.lock();
0866             }
0867         }
0868 
0869         //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one
0870 
0871         QSet<IndexedString> unloadedNames;
0872         bool unloadedOne = true;
0873 
0874         bool unloadAllUnreferenced = !retries;
0875 
0876         //Now unload contexts, but only ones that are not imported by any other currently loaded context
0877         //The complication: Since during the lock-break new references may be added, we must never keep
0878         //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any
0879         //currently loaded contexts. In case of loops, we have to unload everything at once.
0880         while (unloadedOne) {
0881             unloadedOne = false;
0882             int hadUnloadable = 0;
0883 
0884 unloadContexts:
0885 
0886             const auto currentWorkOnContexts = workOnContexts;
0887             for (TopDUContext * unload : currentWorkOnContexts) {
0888                 bool hasReference = false;
0889 
0890                 {
0891                     QMutexLocker l(&m_referenceCountsMutex);
0892                     //Test if the context is imported by a referenced one
0893                     for (auto it = m_referenceCounts.constBegin(), end = m_referenceCounts.constEnd(); it != end;
0894                          ++it) {
0895                         auto* context = it.key();
0896                         if (context == unload || context->imports(unload, CursorInRevision())) {
0897                             workOnContexts.remove(unload);
0898                             hasReference = true;
0899                         }
0900                     }
0901                 }
0902 
0903                 if (!hasReference)
0904                     ++hadUnloadable; //We have found a context that is not referenced
0905                 else
0906                     continue; //This context is referenced
0907 
0908                 bool isImportedByLoaded = !unload->loadedImporters().isEmpty();
0909 
0910                 //If we unload a context that is imported by other contexts, we create a bad loaded state
0911                 if (isImportedByLoaded && !unloadAllUnreferenced)
0912                     continue;
0913 
0914                 unloadedNames.insert(unload->url());
0915                 //Since we've released the write-lock in between, we've got to call store() again to be sure that none of the data is dynamic
0916                 //If nothing has changed, it is only a low-cost call.
0917                 unload->m_dynamicData->store();
0918                 Q_ASSERT(!unload->d_func()->m_dynamic);
0919                 removeDocumentChainFromMemory(unload);
0920                 workOnContexts.remove(unload);
0921                 unloadedOne = true;
0922 
0923                 if (!unloadAllUnreferenced) {
0924                     //Eventually give other threads a chance to access the duchain
0925                     writeLock.unlock();
0926                     //Sleep to give the other threads a realistic chance to get a read-lock in between
0927                     QThread::usleep(500);
0928                     writeLock.lock();
0929                 }
0930             }
0931 
0932             if (hadUnloadable && !unloadedOne) {
0933                 Q_ASSERT(!unloadAllUnreferenced);
0934                 //This can happen in case of loops. We have o unload everything at one time.
0935                 qCDebug(LANGUAGE) << "found" << hadUnloadable <<
0936                     "unloadable contexts, but could not unload separately. Unloading atomically.";
0937                 unloadAllUnreferenced = true;
0938                 hadUnloadable = 0; //Reset to 0, so we cannot loop forever
0939                 goto unloadContexts;
0940             }
0941         }
0942 
0943         if (retries == 0) {
0944             QMutexLocker lock(&m_chainsMutex);
0945             //Do this atomically, since we must be sure that _everything_ is already saved
0946             for (QMultiMap<IndexedString, ParsingEnvironmentFilePointer>::iterator it =
0947                      m_fileEnvironmentInformations.begin();
0948                  it != m_fileEnvironmentInformations.end();) {
0949                 ParsingEnvironmentFile* f = it->data();
0950                 Q_ASSERT(f->d_func()->classId);
0951                 if (f->ref.loadRelaxed() == 1) {
0952                     Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before
0953                     //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any
0954                     //loaded top-context, so just remove it to save some memory and processing time.
0955                     ///@todo use some kind of timeout before removing
0956                     it = m_fileEnvironmentInformations.erase(it);
0957                 } else {
0958                     ++it;
0959                 }
0960             }
0961         }
0962 
0963         if (retries)
0964             writeLock.unlock();
0965 
0966         //This must be the last step, due to the on-disk reference counting
0967         globalItemRepositoryRegistry().store(); //Stores all repositories
0968 
0969         {
0970             //Store the static parsing-environment file data
0971             ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data
0972             Q_ASSERT(ParsingEnvironmentFile::m_staticData);
0973             QFile f(globalItemRepositoryRegistry().path() + QLatin1String("/parsing_environment_data"));
0974             bool opened = f.open(QIODevice::WriteOnly);
0975             Q_ASSERT(opened);
0976             Q_UNUSED(opened);
0977             f.write(reinterpret_cast<const char*>(ParsingEnvironmentFile::m_staticData), sizeof(StaticParsingEnvironmentData));
0978         }
0979 
0980         ///Write out the list of available top-context indices
0981         {
0982             QMutexLocker lock(&m_chainsMutex);
0983 
0984             QFile f(globalItemRepositoryRegistry().path() + QLatin1String("/available_top_context_indices"));
0985             bool opened = f.open(QIODevice::WriteOnly);
0986             Q_ASSERT(opened);
0987             Q_UNUSED(opened);
0988 
0989             f.write(reinterpret_cast<const char*>(m_availableTopContextIndices.data()), m_availableTopContextIndices.size() * sizeof(uint));
0990         }
0991 
0992         if (retries) {
0993             doMoreCleanup(retries - 1, NoLock);
0994             writeLock.lock();
0995         }
0996 
0997         if (lockFlag != NoLock) {
0998             globalItemRepositoryRegistry().unlockForWriting();
0999 
1000             const auto elapsedMS = startTime.msecsTo(QTime::currentTime());
1001             qCDebug(LANGUAGE) << "time spent doing cleanup:" << elapsedMS << "ms - top-contexts still open:" <<
1002                 m_chainsByUrl.size() << "- retries" << retries;
1003         }
1004 
1005         for (QReadWriteLock* lock : qAsConst(locked)) {
1006             lock->unlock();
1007         }
1008 
1009 #if HAVE_MALLOC_TRIM
1010         // trim unused memory but keep a pad buffer of about 50 MB
1011         // this can greatly decrease the perceived memory consumption of kdevelop
1012         // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827
1013         malloc_trim(50 * 1024 * 1024);
1014 #endif
1015     }
1016 
1017     ///Checks whether the information is already loaded.
1018     ParsingEnvironmentFile* findInformation(uint topContextIndex)
1019     {
1020         QMutexLocker lock(&m_chainsMutex);
1021         QHash<uint, ParsingEnvironmentFilePointer>::iterator it = m_indexEnvironmentInformations.find(topContextIndex);
1022         if (it != m_indexEnvironmentInformations.end())
1023             return (*it).data();
1024         return nullptr;
1025     }
1026 
1027     ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists
1028     ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O
1029     ///@warning no other mutexes should be locked, as that may lead to a dedalock
1030     ParsingEnvironmentFile* loadInformation(uint topContextIndex)
1031     {
1032         ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex);
1033         if (alreadyLoaded)
1034             return alreadyLoaded;
1035 
1036         // Step two: Check if it is on disk, and if is, load it
1037         //  TODO: this looks pretty dubious, shouldn't we keep the repo locked while operating on the item?
1038         const auto item = LockedItemRepository::read<EnvironmentInformation>(
1039             [req = EnvironmentInformationRequest(topContextIndex)](const EnvironmentInformationRepo& repo) {
1040                 return repo.findItem(req);
1041             });
1042         if (!item) {
1043             //No environment-information stored for this top-context
1044             return nullptr;
1045         }
1046 
1047         QMutexLocker lock(&m_chainsMutex);
1048 
1049         //Due to multi-threading, we must do this check after locking the mutex, so we can be sure we don't create the same item twice at the same time
1050         alreadyLoaded = findInformation(topContextIndex);
1051         if (alreadyLoaded)
1052             return alreadyLoaded;
1053 
1054         ///FIXME: ugly, and remove const_cast
1055         auto* ret = dynamic_cast<ParsingEnvironmentFile*>(
1056             DUChainItemSystem::self().create(const_cast<DUChainBaseData*>(reinterpret_cast<const DUChainBaseData*>(
1057                 reinterpret_cast<const char*>(item) + sizeof(EnvironmentInformationItem)))));
1058         if (ret) {
1059             Q_ASSERT(ret->d_func()->classId);
1060             Q_ASSERT(ret->indexedTopContext().index() == topContextIndex);
1061             ParsingEnvironmentFilePointer retPtr(ret);
1062 
1063             m_fileEnvironmentInformations.insert(ret->url(), retPtr);
1064 
1065             Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index()));
1066             m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr);
1067         }
1068         return ret;
1069     }
1070 
1071     struct CleanupListVisitor
1072     {
1073         QList<uint> checkContexts;
1074         bool operator()(const EnvironmentInformationItem* item)
1075         {
1076             checkContexts << item->m_topContext;
1077             return true;
1078         }
1079     };
1080 
1081     ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date
1082     void cleanupTopContexts()
1083     {
1084         DUChainWriteLocker lock(DUChain::lock());
1085         qCDebug(LANGUAGE) << "cleaning top-contexts";
1086         CleanupListVisitor visitor;
1087         uint startPos = 0;
1088         LockedItemRepository::write<EnvironmentInformation>([&visitor](EnvironmentInformationRepo& repo) {
1089             repo.visitAllItems(visitor);
1090         });
1091 
1092         int checkContextsCount = maxFinalCleanupCheckContexts;
1093         int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage;
1094 
1095         if (checkContextsCount < percentageOfContexts)
1096             checkContextsCount = percentageOfContexts;
1097 
1098         if (visitor.checkContexts.size() > (int)checkContextsCount)
1099             startPos = QRandomGenerator::global()->bounded(visitor.checkContexts.size() - checkContextsCount);
1100 
1101         int endPos = startPos + maxFinalCleanupCheckContexts;
1102         if (endPos > visitor.checkContexts.size())
1103             endPos = visitor.checkContexts.size();
1104         QSet<uint> check;
1105         for (int a = startPos; a < endPos && check.size() < checkContextsCount; ++a)
1106             if (check.size() < checkContextsCount)
1107                 addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a]));
1108 
1109         for (uint topIndex : qAsConst(check)) {
1110             IndexedTopDUContext top(topIndex);
1111             if (top.data()) {
1112                 qCDebug(LANGUAGE) << "removing top-context for" << top.data()->url().str() <<
1113                     "because it is out of date";
1114                 instance->removeDocumentChain(top.data());
1115             }
1116         }
1117 
1118         qCDebug(LANGUAGE) << "check ready";
1119     }
1120 
1121 private:
1122 
1123     void addContextsForRemoval(QSet<uint>& topContexts, IndexedTopDUContext top)
1124     {
1125         if (topContexts.contains(top.index()))
1126             return;
1127 
1128         QExplicitlySharedDataPointer<ParsingEnvironmentFile> info(instance->environmentFileForDocument(top));
1129         ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...)
1130         if (info && info->needsUpdate()) {
1131             //This context will be removed
1132         } else {
1133             return;
1134         }
1135 
1136         topContexts.insert(top.index());
1137 
1138         if (info) {
1139             //Check whether importers need to be removed as well
1140             const QList<QExplicitlySharedDataPointer<ParsingEnvironmentFile>> importers = info->importers();
1141 
1142             QSet<QExplicitlySharedDataPointer<ParsingEnvironmentFile>> checkNext;
1143 
1144             //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached
1145 
1146             for (auto& importer : importers) {
1147                 IndexedTopDUContext c = importer->indexedTopContext();
1148                 if (!topContexts.contains(c.index())) {
1149                     topContexts.insert(c.index()); //Prevent useless recursion
1150                     checkNext.insert(importer);
1151                 }
1152             }
1153 
1154             for (auto& parsingEnvFile : qAsConst(checkNext)) {
1155                 topContexts.remove(parsingEnvFile->indexedTopContext().index()); // Enable full check again
1156                 addContextsForRemoval(topContexts, parsingEnvFile->indexedTopContext());
1157             }
1158         }
1159     }
1160 
1161     ///Stores the environment-information for the given url
1162     void storeInformationList(const IndexedString& url)
1163     {
1164         EnvironmentInformationListItem newItem;
1165         newItem.m_file = url;
1166 
1167         QSet<uint> newItems;
1168 
1169         {
1170             QMutexLocker lock(&m_chainsMutex);
1171             QMultiMap<IndexedString,
1172                 ParsingEnvironmentFilePointer>::iterator start = m_fileEnvironmentInformations.lowerBound(url);
1173             QMultiMap<IndexedString,
1174                 ParsingEnvironmentFilePointer>::iterator end = m_fileEnvironmentInformations.upperBound(url);
1175 
1176             for (QMultiMap<IndexedString, ParsingEnvironmentFilePointer>::iterator it = start; it != end; ++it) {
1177                 uint topContextIndex = (*it)->indexedTopContext().index();
1178                 newItems.insert(topContextIndex);
1179                 newItem.itemsList().append(topContextIndex);
1180             }
1181         }
1182 
1183         LockedItemRepository::write<EnvironmentInformationList>(
1184             [&, request = EnvironmentInformationListRequest(url)](EnvironmentInformationListRepo& repo) mutable {
1185                 const uint index = repo.findIndex(request);
1186 
1187                 if (index) {
1188                     // We only handle adding items here, since we can never be sure whether everything is loaded
1189                     // Removal is handled directly in removeEnvironmentInformation
1190 
1191                     const EnvironmentInformationListItem* item = repo.itemFromIndex(index);
1192                     QSet<uint> oldItems;
1193                     FOREACH_FUNCTION(uint topContextIndex, item->items)
1194                     {
1195                         oldItems.insert(topContextIndex);
1196                         if (!newItems.contains(topContextIndex)) {
1197                             newItems.insert(topContextIndex);
1198                             newItem.itemsList().append(topContextIndex);
1199                         }
1200                     }
1201 
1202                     if (oldItems == newItems)
1203                         return;
1204 
1205                     /// Update/insert a new list
1206                     repo.deleteItem(index); // Remove the previous item
1207                 }
1208 
1209                 Q_ASSERT(repo.findIndex(request) == 0);
1210 
1211                 // Insert the new item
1212                 request.m_item = &newItem;
1213                 repo.index(request);
1214 
1215                 Q_ASSERT(repo.findIndex(EnvironmentInformationListRequest(url)));
1216             });
1217     }
1218 
1219     //Loaded environment information. Protected by m_chainsMutex
1220     QMultiMap<IndexedString, ParsingEnvironmentFilePointer> m_fileEnvironmentInformations;
1221     QHash<uint, ParsingEnvironmentFilePointer> m_indexEnvironmentInformations;
1222 };
1223 
1224 Q_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate)
1225 
1226 DUChain::DUChain()
1227 {
1228     Q_ASSERT(ICore::self());
1229 
1230     connect(
1231         ICore::self()->documentController(), &IDocumentController::documentLoadedPrepare, this,
1232         &DUChain::documentLoadedPrepare);
1233     connect(
1234         ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this,
1235         &DUChain::documentRenamed);
1236     connect(
1237         ICore::self()->documentController(), &IDocumentController::documentActivated, this,
1238         &DUChain::documentActivated);
1239     connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &DUChain::documentClosed);
1240 }
1241 
1242 DUChain::~DUChain()
1243 {
1244     DUChain::m_deleted = true;
1245 }
1246 
1247 DUChain* DUChain::self()
1248 {
1249     return sdDUChainPrivate->instance;
1250 }
1251 
1252 extern void initModificationRevisionSetRepository();
1253 extern void initDeclarationRepositories();
1254 extern void initIdentifierRepository();
1255 extern void initTypeRepository();
1256 extern void initInstantiationInformationRepository();
1257 
1258 QString DUChain::repositoryPathForSession(const KDevelop::ISessionLock::Ptr& session)
1259 {
1260     QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
1261     cacheDir += QLatin1String("/kdevduchain");
1262     QString baseDir = QProcessEnvironment::systemEnvironment().value(QStringLiteral("KDEV_DUCHAIN_DIR"), cacheDir);
1263     baseDir += QStringLiteral("/%1-%2").arg(QCoreApplication::applicationName(), session->id());
1264     return baseDir;
1265 }
1266 
1267 void DUChain::initialize()
1268 {
1269     // Initialize the global item repository as first thing after loading the session
1270     Q_ASSERT(ICore::self());
1271     Q_ASSERT(ICore::self()->activeSession());
1272 
1273     ItemRepositoryRegistry::initialize(repositoryPathForSession(ICore::self()->activeSessionLock()));
1274 
1275     initReferenceCounting();
1276 
1277     // This needs to be initialized here too as the function is not threadsafe, but can
1278     // sometimes be called from different threads. This results in the underlying QFile
1279     // being 0 and hence crashes at some point later when accessing the contents via
1280     // read. See https://bugs.kde.org/show_bug.cgi?id=250779
1281     RecursiveImportRepository::repository();
1282 
1283     LockedItemRepository::initialize<EnvironmentInformationList>();
1284     LockedItemRepository::initialize<EnvironmentInformation>();
1285 
1286     // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323
1287     initDeclarationRepositories();
1288 
1289     initModificationRevisionSetRepository();
1290     initIdentifierRepository();
1291     initTypeRepository();
1292     initInstantiationInformationRepository();
1293 
1294     PersistentSymbolTable::self();
1295     Importers::self();
1296     CodeModel::self();
1297 
1298     globalImportIdentifier();
1299     globalIndexedImportIdentifier();
1300     globalAliasIdentifier();
1301     globalIndexedAliasIdentifier();
1302 }
1303 
1304 DUChainLock* DUChain::lock()
1305 {
1306     return &sdDUChainPrivate->lock;
1307 }
1308 
1309 QList<TopDUContext*> DUChain::allChains() const
1310 {
1311     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1312     return sdDUChainPrivate->m_chainsByUrl.values();
1313 }
1314 
1315 void DUChain::updateContextEnvironment(TopDUContext* context, ParsingEnvironmentFile* file)
1316 {
1317     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1318 
1319     removeFromEnvironmentManager(context);
1320 
1321     context->setParsingEnvironmentFile(file);
1322 
1323     addToEnvironmentManager(context);
1324 }
1325 
1326 void DUChain::removeDocumentChain(TopDUContext* context)
1327 {
1328     ENSURE_CHAIN_WRITE_LOCKED;
1329     IndexedTopDUContext indexed(context->indexed());
1330     Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain
1331     context->m_dynamicData->deleteOnDisk();
1332     Q_ASSERT(indexed.data() == context);
1333     sdDUChainPrivate->removeDocumentChainFromMemory(context);
1334     Q_ASSERT(!indexed.data());
1335     Q_ASSERT(!environmentFileForDocument(indexed));
1336 
1337     QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex);
1338     sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index());
1339 }
1340 
1341 void DUChain::addDocumentChain(TopDUContext* chain)
1342 {
1343     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1344 
1345 //   qCDebug(LANGUAGE) << "duchain: adding document" << chain->url().str() << " " << chain;
1346     Q_ASSERT(chain);
1347 
1348     Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex()));
1349 
1350     {
1351         QMutexLocker lock(&DUChain::chainsByIndexLock);
1352         if (DUChain::chainsByIndex.size() <= chain->ownIndex())
1353             DUChain::chainsByIndex.resize(chain->ownIndex() + 100, nullptr);
1354 
1355         DUChain::chainsByIndex[chain->ownIndex()] = chain;
1356     }
1357     {
1358         Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]);
1359     }
1360     Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex()));
1361 
1362     sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain);
1363 
1364     Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex()));
1365 
1366     chain->setInDuChain(true);
1367 
1368     l.unlock();
1369 
1370     addToEnvironmentManager(chain);
1371 
1372     // This function might be called during shutdown by stale parse jobs
1373     // Make sure we don't access null-pointers here
1374     if (ICore::self() && ICore::self()->languageController() &&
1375         ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) {
1376         //Make sure the context stays alive at least as long as the context is open
1377         ReferencedTopDUContext ctx(chain);
1378         sdDUChainPrivate->m_openDocumentContexts.insert(ctx);
1379     }
1380 }
1381 
1382 void DUChain::addToEnvironmentManager(TopDUContext* chain)
1383 {
1384     ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile();
1385     if (!file)
1386         return; //We don't need to manage
1387 
1388     Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex());
1389 
1390     if (ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) {
1391         ///If this triggers, there has already been another environment-information registered for this top-context.
1392         ///removeFromEnvironmentManager should have been called before to remove the old environment-information.
1393         Q_ASSERT(alreadyHave == file.data());
1394         Q_UNUSED(alreadyHave);
1395         return;
1396     }
1397 
1398     sdDUChainPrivate->addEnvironmentInformation(file);
1399 }
1400 
1401 void DUChain::removeFromEnvironmentManager(TopDUContext* chain)
1402 {
1403     ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile();
1404     if (!file)
1405         return; //We don't need to manage
1406 
1407     sdDUChainPrivate->removeEnvironmentInformation(file);
1408 }
1409 
1410 TopDUContext* DUChain::chainForDocument(const QUrl& document, bool proxyContext) const
1411 {
1412     return chainForDocument(IndexedString(document), proxyContext);
1413 }
1414 
1415 bool DUChain::isInMemory(uint topContextIndex) const
1416 {
1417     return DUChainPrivate::hasChainForIndex(topContextIndex);
1418 }
1419 
1420 IndexedString DUChain::urlForIndex(uint index) const
1421 {
1422     {
1423         TopDUContext* chain = DUChainPrivate::readChainForIndex(index);
1424         if (chain)
1425             return chain->url();
1426     }
1427 
1428     return TopDUContextDynamicData::loadUrl(index);
1429 }
1430 
1431 TopDUContext* DUChain::loadChain(uint index)
1432 {
1433     QSet<uint> loaded;
1434     sdDUChainPrivate->loadChain(index, loaded);
1435 
1436     {
1437         QMutexLocker lock(&chainsByIndexLock);
1438 
1439         if (chainsByIndex.size() > index) {
1440             TopDUContext* top = chainsByIndex[index];
1441             if (top)
1442                 return top;
1443         }
1444     }
1445 
1446     return nullptr;
1447 }
1448 
1449 TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const
1450 {
1451     ENSURE_CHAIN_READ_LOCKED;
1452 
1453     if (sdDUChainPrivate->m_destroyed)
1454         return nullptr;
1455 
1456     const QList<ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document);
1457 
1458     for (const ParsingEnvironmentFilePointer& file : list) {
1459         if (isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) {
1460             return file->topContext();
1461         }
1462     }
1463 
1464     for (const ParsingEnvironmentFilePointer& file : list) {
1465         if (proxyContext == file->isProxyContext()) {
1466             return file->topContext();
1467         }
1468     }
1469 
1470     //Allow selecting a top-context even if there is no ParsingEnvironmentFile
1471     const QList<TopDUContext*> ret = chainsForDocument(document);
1472     for (TopDUContext* ctx : ret) {
1473         if (!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext))
1474             return ctx;
1475     }
1476 
1477     return nullptr;
1478 }
1479 
1480 QList<TopDUContext*> DUChain::chainsForDocument(const QUrl& document) const
1481 {
1482     return chainsForDocument(IndexedString(document));
1483 }
1484 
1485 QList<TopDUContext*> DUChain::chainsForDocument(const IndexedString& document) const
1486 {
1487     QList<TopDUContext*> chains;
1488 
1489     if (sdDUChainPrivate->m_destroyed)
1490         return chains;
1491 
1492     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1493 
1494     // Match all parsed versions of this document
1495     for (auto it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end();
1496          ++it) {
1497         if (it.key() == document)
1498             chains << it.value();
1499         else
1500             break;
1501     }
1502 
1503     return chains;
1504 }
1505 
1506 TopDUContext* DUChain::chainForDocument(const QUrl& document, const KDevelop::ParsingEnvironment* environment,
1507                                         bool proxyContext) const
1508 {
1509     return chainForDocument(IndexedString(document), environment, proxyContext);
1510 }
1511 
1512 ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(const IndexedString& document,
1513                                                                   const ParsingEnvironment* environment,
1514                                                                   bool proxyContext) const
1515 {
1516     ENSURE_CHAIN_READ_LOCKED;
1517 
1518     if (sdDUChainPrivate->m_destroyed)
1519         return ParsingEnvironmentFilePointer();
1520     const QList<ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document);
1521 
1522 //    qCDebug(LANGUAGE) << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts"));
1523 
1524     for (auto& envFilePtr : list) {
1525         if (envFilePtr && (envFilePtr->isProxyContext() == proxyContext) && envFilePtr->matchEnvironment(environment) &&
1526             // Verify that the environment-file and its top-context are "good": The top-context must exist,
1527             // and there must be a content-context associated to the proxy-context.
1528             envFilePtr->topContext() &&
1529             (!proxyContext || DUChainUtils::contentContextFromProxyContext(envFilePtr->topContext()))) {
1530             return envFilePtr;
1531         }
1532     }
1533     return ParsingEnvironmentFilePointer();
1534 }
1535 
1536 QList<ParsingEnvironmentFilePointer> DUChain::allEnvironmentFiles(const IndexedString& document)
1537 {
1538     return sdDUChainPrivate->getEnvironmentInformation(document);
1539 }
1540 
1541 ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const
1542 {
1543     if (topContext.index() == 0)
1544         return ParsingEnvironmentFilePointer();
1545 
1546     return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index()));
1547 }
1548 
1549 TopDUContext* DUChain::chainForDocument(const IndexedString& document, const ParsingEnvironment* environment,
1550                                         bool proxyContext) const
1551 {
1552     if (sdDUChainPrivate->m_destroyed)
1553         return nullptr;
1554     ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext);
1555     if (envFile) {
1556         return envFile->topContext();
1557     } else {
1558         return nullptr;
1559     }
1560 }
1561 
1562 QList<QUrl> DUChain::documents() const
1563 {
1564     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1565 
1566     QList<QUrl> ret;
1567     ret.reserve(sdDUChainPrivate->m_chainsByUrl.count());
1568     for (TopDUContext* top : qAsConst(sdDUChainPrivate->m_chainsByUrl)) {
1569         ret << top->url().toUrl();
1570     }
1571 
1572     return ret;
1573 }
1574 
1575 QList<IndexedString> DUChain::indexedDocuments() const
1576 {
1577     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1578 
1579     QList<IndexedString> ret;
1580     ret.reserve(sdDUChainPrivate->m_chainsByUrl.count());
1581     for (TopDUContext* top : qAsConst(sdDUChainPrivate->m_chainsByUrl)) {
1582         ret << top->url();
1583     }
1584 
1585     return ret;
1586 }
1587 
1588 void DUChain::documentActivated(KDevelop::IDocument* doc)
1589 {
1590     if (sdDUChainPrivate->m_destroyed)
1591         return;
1592 
1593     DUChainReadLocker lock(DUChain::lock());
1594     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1595 
1596     auto backgroundParser = ICore::self()->languageController()->backgroundParser();
1597     auto addWithHighPriority = [backgroundParser, doc]() {
1598                                    backgroundParser->addDocument(IndexedString(doc->url()),
1599                                                                  TopDUContext::VisibleDeclarationsAndContexts,
1600                                                                  BackgroundParser::BestPriority);
1601                                };
1602 
1603     TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true);
1604     //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated.
1605     //If yes, update it.
1606     if (ctx && ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->needsUpdate()) {
1607         qCDebug(LANGUAGE) << "Document needs update, using best priority since it just got activated:" << doc->url();
1608         addWithHighPriority();
1609     } else if (backgroundParser->managedDocuments().contains(IndexedString(doc->url()))) {
1610         // increase priority if there's already parse job of this document in the queue
1611         qCDebug(LANGUAGE) << "Prioritizing activated document:" << doc->url();
1612         addWithHighPriority();
1613     }
1614 }
1615 
1616 void DUChain::documentClosed(IDocument* document)
1617 {
1618     if (sdDUChainPrivate->m_destroyed)
1619         return;
1620 
1621     IndexedString url(document->url());
1622 
1623     const auto currentDocumentContexts = sdDUChainPrivate->m_openDocumentContexts;
1624     for (const ReferencedTopDUContext& top : currentDocumentContexts) {
1625         if (top->url() == url)
1626             sdDUChainPrivate->m_openDocumentContexts.remove(top);
1627     }
1628 }
1629 
1630 void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc)
1631 {
1632     if (sdDUChainPrivate->m_destroyed)
1633         return;
1634 
1635     const IndexedString url(doc->url());
1636     DUChainWriteLocker lock(DUChain::lock());
1637     QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
1638 
1639     TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url());
1640     QList<TopDUContext*> chains = chainsForDocument(url);
1641 
1642     const auto languages = ICore::self()->languageController()->languagesForUrl(doc->url());
1643 
1644     if (standardContext) {
1645         Q_ASSERT(chains.contains(standardContext)); //We have just loaded it
1646         Q_ASSERT((standardContext->url() == url));
1647 
1648         sdDUChainPrivate->m_openDocumentContexts.insert(standardContext);
1649 
1650         bool needsUpdate = standardContext->parsingEnvironmentFile() &&
1651                            standardContext->parsingEnvironmentFile()->needsUpdate();
1652         if (!needsUpdate) {
1653             //Only apply the highlighting if we don't need to update, else we might highlight total crap
1654             //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long
1655             //Else the highlighting will be done in the background-thread
1656             //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses
1657             //but it approximates the correct behavior.
1658             bool allImportsLoaded = true;
1659             const auto importedParentContexts = standardContext->importedParentContexts();
1660             for (const DUContext::Import& import : importedParentContexts) {
1661                 if (!import.indexedContext().indexedTopContext().isLoaded())
1662                     allImportsLoaded = false;
1663             }
1664 
1665             if (allImportsLoaded) {
1666                 l.unlock();
1667                 lock.unlock();
1668                 for (const auto language : languages) {
1669                     if (language->codeHighlighting()) {
1670                         language->codeHighlighting()->highlightDUChain(standardContext);
1671                     }
1672                 }
1673 
1674                 qCDebug(LANGUAGE) << "highlighted" << doc->url() << "in foreground";
1675                 return;
1676             }
1677         } else {
1678             qCDebug(LANGUAGE) << "not highlighting the duchain because the documents needs an update";
1679         }
1680 
1681         if (needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) {
1682             ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()),
1683                                                                                  TopDUContext::AllDeclarationsContextsAndUses
1684                                                                                  | TopDUContext::ForceUpdate);
1685             return;
1686         }
1687     }
1688 
1689     //Add for highlighting etc.
1690     ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(
1691                                                                              doc->url()),
1692                                                                          TopDUContext::AllDeclarationsContextsAndUses);
1693 }
1694 
1695 void DUChain::documentRenamed(KDevelop::IDocument* doc)
1696 {
1697     if (sdDUChainPrivate->m_destroyed)
1698         return;
1699 
1700     const auto url = doc->url();
1701     // url is invalid when a file open in KDevelop is deleted externally, then the user
1702     // closes the file by clicking the Close File button on KTextEditor's prompt.
1703     if (url.isValid()) {
1704         ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString{url},
1705                                                                              TopDUContext::AllDeclarationsContextsAndUses
1706                                                                              | TopDUContext::ForceUpdate);
1707     }
1708 }
1709 
1710 Uses* DUChain::uses()
1711 {
1712     return &sdDUChainPrivate->m_uses;
1713 }
1714 
1715 Definitions* DUChain::definitions()
1716 {
1717     return &sdDUChainPrivate->m_definitions;
1718 }
1719 
1720 static void finalCleanup()
1721 {
1722     DUChainWriteLocker writeLock(DUChain::lock());
1723     qCDebug(LANGUAGE) << "doing final cleanup";
1724 
1725     int cleaned = 0;
1726     while ((cleaned = globalItemRepositoryRegistry().finalCleanup())) {
1727         qCDebug(LANGUAGE) << "cleaned" << cleaned << "B";
1728         if (cleaned < 1000) {
1729             qCDebug(LANGUAGE) << "cleaned enough";
1730             break;
1731         }
1732     }
1733     qCDebug(LANGUAGE) << "final cleanup ready";
1734 }
1735 
1736 void DUChain::shutdown()
1737 {
1738     // if core is not shutting down, we can end up in deadlocks or crashes
1739     // since language plugins might still try to access static duchain stuff
1740     Q_ASSERT(!ICore::self() || ICore::self()->shuttingDown());
1741 
1742     qCDebug(LANGUAGE) << "Cleaning up and shutting down DUChain";
1743 
1744     QMutexLocker lock(&sdDUChainPrivate->cleanupMutex());
1745 
1746     {
1747         //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded
1748         //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes
1749         globalItemRepositoryRegistry().lockForWriting();
1750         sdDUChainPrivate->cleanupTopContexts();
1751         globalItemRepositoryRegistry().unlockForWriting();
1752     }
1753 
1754     sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data
1755 
1756     sdDUChainPrivate->m_openDocumentContexts.clear();
1757     sdDUChainPrivate->m_destroyed = true;
1758     sdDUChainPrivate->clear();
1759 
1760     {
1761         //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded
1762         //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes
1763         globalItemRepositoryRegistry().lockForWriting();
1764         finalCleanup();
1765         globalItemRepositoryRegistry().unlockForWriting();
1766     }
1767 
1768     globalItemRepositoryRegistry().shutdown();
1769 }
1770 
1771 uint DUChain::newTopContextIndex()
1772 {
1773     {
1774         QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex);
1775         if (!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) {
1776             uint ret = sdDUChainPrivate->m_availableTopContextIndices.back();
1777             sdDUChainPrivate->m_availableTopContextIndices.pop_back();
1778             if (TopDUContextDynamicData::fileExists(ret)) {
1779                 qCWarning(LANGUAGE) << "Problem in the management of available top-context indices";
1780                 return newTopContextIndex();
1781             }
1782             return ret;
1783         }
1784     }
1785     static QAtomicInt& currentId(globalItemRepositoryRegistry().customCounter(QStringLiteral("Top-Context Counter"),
1786                                                                               1));
1787     return currentId.fetchAndAddRelaxed(1);
1788 }
1789 
1790 void DUChain::refCountUp(TopDUContext* top)
1791 {
1792     QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex);
1793     // note: value is default-constructed to zero if it does not exist
1794     ++sdDUChainPrivate->m_referenceCounts[top];
1795 }
1796 
1797 bool DUChain::deleted()
1798 {
1799     return m_deleted;
1800 }
1801 
1802 void DUChain::refCountDown(TopDUContext* top)
1803 {
1804     QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex);
1805     auto it = sdDUChainPrivate->m_referenceCounts.find(top);
1806     if (it == sdDUChainPrivate->m_referenceCounts.end()) {
1807         //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced";
1808         return;
1809     }
1810     auto& refCount = *it;
1811     --refCount;
1812     if (!refCount) {
1813         sdDUChainPrivate->m_referenceCounts.erase(it);
1814     }
1815 }
1816 
1817 void DUChain::emitDeclarationSelected(const DeclarationPointer& decl)
1818 {
1819     if (sdDUChainPrivate->m_destroyed)
1820         return;
1821 
1822     emit declarationSelected(decl);
1823 }
1824 
1825 void DUChain::emitUpdateReady(const IndexedString& url, const ReferencedTopDUContext& topContext)
1826 {
1827     if (sdDUChainPrivate->m_destroyed)
1828         return;
1829 
1830     emit updateReady(url, topContext);
1831 }
1832 
1833 KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document,
1834                                                         KDevelop::TopDUContext::Features minFeatures, bool proxyContext)
1835 {
1836     Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock());
1837 
1838     WaitForUpdate waiter;
1839     updateContextForUrl(document, minFeatures, &waiter);
1840 
1841     while (!waiter.m_ready) {
1842         // we might have been shut down in the meanwhile
1843         if (!ICore::self()) {
1844             return nullptr;
1845         }
1846 
1847         QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments");
1848         QCoreApplication::processEvents();
1849         QThread::usleep(1000);
1850     }
1851 
1852     if (!proxyContext) {
1853         DUChainReadLocker readLock(DUChain::lock());
1854         return DUChainUtils::contentContextFromProxyContext(waiter.m_topContext);
1855     }
1856 
1857     return waiter.m_topContext;
1858 }
1859 
1860 void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures,
1861                                   QObject* notifyReady, int priority) const
1862 {
1863     // the call to DUChainUtils::standardContextForUrl(...) takes surprisingly long
1864     // however if a document is already scheduled for parsing, we know it needs parsing and can
1865     // skip the expansive check.
1866     auto* backgroundParser = ICore::self()->languageController()->backgroundParser();
1867     // atomically check if the document is already scheduled and if so add a new listener:
1868     bool listenerAdded = backgroundParser->addListenerToDocumentIfExist(document, minFeatures, priority, notifyReady);
1869     // if the document isn't already scheduled and the listener was added:
1870     if (!listenerAdded)
1871     {
1872         DUChainReadLocker lock(DUChain::lock());
1873         // check if the document needs parsing
1874         TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl());
1875         if (standardContext && standardContext->parsingEnvironmentFile() &&
1876             !standardContext->parsingEnvironmentFile()->needsUpdate() &&
1877             standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) {
1878             // if the document doesn't need parsing we can immedistely return the already parsed context to the listener
1879             lock.unlock();
1880             if (notifyReady) {
1881                 // do not remove qualification KDevelop:: or invokeMethod will not find the proper method
1882                 QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection,
1883                                 Q_ARG(KDevelop::IndexedString, document),
1884                                 Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext)));
1885             }
1886         } else {
1887             ///Start a parse-job for the given document
1888             lock.unlock();
1889             backgroundParser->addDocument(document, minFeatures, priority, notifyReady);
1890         }
1891     }
1892 }
1893 
1894 void DUChain::disablePersistentStorage(bool disable)
1895 {
1896     sdDUChainPrivate->m_cleanupDisabled = disable;
1897 }
1898 
1899 void DUChain::storeToDisk()
1900 {
1901     bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled;
1902     sdDUChainPrivate->m_cleanupDisabled = false;
1903 
1904     sdDUChainPrivate->doMoreCleanup();
1905 
1906     sdDUChainPrivate->m_cleanupDisabled = wasDisabled;
1907 }
1908 
1909 bool DUChain::compareToDisk()
1910 {
1911     DUChainWriteLocker writeLock(DUChain::lock());
1912 
1913     ///Step 1: Compare the repositories
1914     return true;
1915 }
1916 }
1917 
1918 #include "moc_duchain.cpp"