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"