diff --git a/language/backgroundparser/urlparselock.cpp b/language/backgroundparser/urlparselock.cpp index d113b66b38..edc7b7c5c8 100644 --- a/language/backgroundparser/urlparselock.cpp +++ b/language/backgroundparser/urlparselock.cpp @@ -1,59 +1,59 @@ /* * This file is part of KDevelop * * Copyright 2007-2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "urlparselock.h" #include #include using namespace KDevelop; namespace { ///Facilities to prevent multiple parse-jobs from processing the same url. QMutex urlParseMutex; QHash > parsingUrls; } UrlParseLock::UrlParseLock(const IndexedString& url) : m_url(url) { QMutexLocker lock(&urlParseMutex); while(parsingUrls.contains(m_url) && parsingUrls[m_url].first != QThread::currentThread()) { //Wait here until no other thread is updating parsing the url lock.unlock(); - sleep(1); + QThread::sleep(1); lock.relock(); } if(parsingUrls.contains(m_url)) ++parsingUrls[m_url].second; else parsingUrls.insert(m_url, qMakePair(QThread::currentThread(), 1u)); } UrlParseLock::~UrlParseLock() { QMutexLocker lock(&urlParseMutex); Q_ASSERT(parsingUrls.contains(m_url)); Q_ASSERT(parsingUrls[m_url].first == QThread::currentThread()); --parsingUrls[m_url].second; if(parsingUrls[m_url].second == 0) parsingUrls.remove(m_url); } diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index 7ed779d37b..c877084e03 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1701 +1,1701 @@ /* This is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include "duchainlock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.h" #include "util/debug.h" #include "topducontext.h" #include "topducontextdata.h" #include "topducontextdynamicdata.h" #include "parsingenvironment.h" #include "declaration.h" #include "definitions.h" #include "duchainutils.h" #include "use.h" #include "uses.h" #include "abstractfunctiondeclaration.h" #include "duchainregister.h" #include "persistentsymboltable.h" #include "serialization/itemrepository.h" #include "waitforupdate.h" #include "importers.h" namespace { //Additional "soft" cleanup steps that are done before the actual cleanup. //During "soft" cleanup, the consistency is not guaranteed. The repository is //marked to be updating during soft cleanup, so if kdevelop crashes, it will be cleared. //The big advantage of the soft cleanup steps is, that the duchain is always only locked for //short times, which leads to no lockup in the UI. const int SOFT_CLEANUP_STEPS = 1; const uint cleanupEverySeconds = 200; ///Approximate maximum count of top-contexts that are checked during final cleanup const uint maxFinalCleanupCheckContexts = 2000; const uint minimumFinalCleanupCheckContextsPercentage = 10; //Check at least n% of all top-contexts during cleanup //Set to true as soon as the duchain is deleted } namespace KDevelop { bool DUChain::m_deleted = false; ///Must be locked through KDevelop::SpinLock before using chainsByIndex ///This lock should be locked only for very short times -SpinLockData DUChain::chainsByIndexLock; +QMutex DUChain::chainsByIndexLock; std::vector DUChain::chainsByIndex; //This thing is not actually used, but it's needed for compiling DEFINE_LIST_MEMBER_HASH(EnvironmentInformationListItem, items, uint) //An entry for the item-repository that holds some meta-data. Behind this entry, the actual ParsingEnvironmentFileData is stored. class EnvironmentInformationItem { public: EnvironmentInformationItem(uint topContext, uint size) : m_topContext(topContext), m_size(size) { } ~EnvironmentInformationItem() { } unsigned int hash() const { return m_topContext; } unsigned int itemSize() const { return sizeof(*this) + m_size; } uint m_topContext; uint m_size;//Size of the data behind, that holds the actual item }; struct ItemRepositoryIndexHash { uint operator()(unsigned int __x) const { return 173*(__x>>2) + 11 * (__x >> 16); } }; class EnvironmentInformationRequest { public: ///This constructor should only be used for lookup EnvironmentInformationRequest(uint topContextIndex) : m_file(0), m_index(topContextIndex) { } EnvironmentInformationRequest(const ParsingEnvironmentFile* file) : m_file(file), m_index(file->indexedTopContext().index()) { } enum { AverageSize = 32 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_index; } uint itemSize() const { return sizeof(EnvironmentInformationItem) + DUChainItemSystem::self().dynamicSize(*m_file->d_func()); } void createItem(EnvironmentInformationItem* item) const { new (item) EnvironmentInformationItem(m_index, DUChainItemSystem::self().dynamicSize(*m_file->d_func())); Q_ASSERT(m_file->d_func()->m_dynamic); DUChainBaseData* data = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); DUChainItemSystem::self().copy(*m_file->d_func(), *data, true); Q_ASSERT(data->m_range == m_file->d_func()->m_range); Q_ASSERT(data->classId == m_file->d_func()->classId); Q_ASSERT(data->m_dynamic == false); } static void destroy(EnvironmentInformationItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationItem(); //We don't need to call the destructor, because that's done in DUChainBase::makeDynamic() //We just need to make sure that every environment-file is dynamic when it's deleted // DUChainItemSystem::self().callDestructor((DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))); } static bool persistent(const EnvironmentInformationItem* ) { //Cleanup done separately return true; } bool equals(const EnvironmentInformationItem* item) const { return m_index == item->m_topContext; } const ParsingEnvironmentFile* m_file; uint m_index; }; ///A list of environment-information/top-contexts mapped to a file-name class EnvironmentInformationListItem { public: EnvironmentInformationListItem() { initializeAppendedLists(true); } EnvironmentInformationListItem(const EnvironmentInformationListItem& rhs, bool dynamic = true) { initializeAppendedLists(dynamic); m_file = rhs.m_file; copyListsFrom(rhs); } ~EnvironmentInformationListItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return m_file.hash(); } unsigned short int itemSize() const { return dynamicSize(); } IndexedString m_file; uint classSize() const { return sizeof(*this); } START_APPENDED_LISTS(EnvironmentInformationListItem); ///Contains the index of each contained environment-item APPENDED_LIST_FIRST(EnvironmentInformationListItem, uint, items); END_APPENDED_LISTS(EnvironmentInformationListItem, items); }; class EnvironmentInformationListRequest { public: ///This constructor should only be used for lookup EnvironmentInformationListRequest(const IndexedString& file) : m_file(file), m_item(0) { } ///This is used to actually construct the information in the repository EnvironmentInformationListRequest(const IndexedString& file, const EnvironmentInformationListItem& item) : m_file(file), m_item(&item) { } enum { AverageSize = 160 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_file.hash(); } uint itemSize() const { return m_item->itemSize(); } void createItem(EnvironmentInformationListItem* item) const { Q_ASSERT(m_item->m_file == m_file); new (item) EnvironmentInformationListItem(*m_item, false); } static void destroy(EnvironmentInformationListItem* item, KDevelop::AbstractItemRepository&) { item->~EnvironmentInformationListItem(); } static bool persistent(const EnvironmentInformationListItem*) { //Cleanup is done separately return true; } bool equals(const EnvironmentInformationListItem* item) const { return m_file == item->m_file; } IndexedString m_file; const EnvironmentInformationListItem* m_item; }; class DUChainPrivate; static DUChainPrivate* duChainPrivateSelf = 0; class DUChainPrivate { class CleanupThread : public QThread { public: CleanupThread(DUChainPrivate* data) : m_stopRunning(false), m_data(data) { } void stopThread() { { QMutexLocker lock(&m_waitMutex); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit } wait(); } private: void run() { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; QMutexLocker lock(&m_waitMutex); m_wait.wait(&m_waitMutex, 1000); } if(m_stopRunning) break; //Just to make sure the cache is cleared periodically ModificationRevisionSet::clearCache(); m_data->doMoreCleanup(SOFT_CLEANUP_STEPS); if(m_stopRunning) break; } } bool m_stopRunning; QWaitCondition m_wait; QMutex m_waitMutex; DUChainPrivate* m_data; }; public: DUChainPrivate() : m_chainsMutex(QMutex::Recursive), m_cleanupMutex(QMutex::Recursive), instance(0), m_cleanupDisabled(false), m_destroyed(false), m_environmentListInfo("Environment Lists"), m_environmentInfo("Environment Information") { #if defined(TEST_NO_CLEANUP) m_cleanupDisabled = true; #endif duChainPrivateSelf = this; qRegisterMetaType("KDevelop::DUChainBasePointer"); qRegisterMetaType("KDevelop::DUContextPointer"); qRegisterMetaType("KDevelop::TopDUContextPointer"); qRegisterMetaType("KDevelop::DeclarationPointer"); qRegisterMetaType("KDevelop::FunctionDeclarationPointer"); qRegisterMetaType("KDevelop::IndexedString"); qRegisterMetaType("KDevelop::IndexedTopDUContext"); qRegisterMetaType("KDevelop::ReferencedTopDUContext"); instance = new DUChain(); m_cleanup = new CleanupThread(this); m_cleanup->start(); DUChain::m_deleted = false; ///Loading of some static data: { ///@todo Solve this more duchain-like QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::ReadOnly); ///FIXME: ugh, so ugly ParsingEnvironmentFile::m_staticData = reinterpret_cast( new char[sizeof(StaticParsingEnvironmentData)]); if(opened) { qCDebug(LANGUAGE) << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ qCDebug(LANGUAGE) << "creating new parsing-environment static data"; //Initialize new (ParsingEnvironmentFile::m_staticData) StaticParsingEnvironmentData(); } } ///Read in the list of available top-context indices { QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::ReadOnly); if(opened) { Q_ASSERT( (f.size() % sizeof(uint)) == 0); m_availableTopContextIndices.resize(f.size()/sizeof(uint)); f.read((char*)m_availableTopContextIndices.data(), f.size()); } } } ~DUChainPrivate() { qCDebug(LANGUAGE) << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; } void clear() { if(!m_cleanupDisabled) doMoreCleanup(); QMutexLocker l(&m_chainsMutex); DUChainWriteLocker writeLock(DUChain::lock()); foreach(TopDUContext* top, m_chainsByUrl.values()) removeDocumentChainFromMemory(top); m_indexEnvironmentInformations.clear(); m_fileEnvironmentInformations.clear(); Q_ASSERT(m_fileEnvironmentInformations.isEmpty()); Q_ASSERT(m_chainsByUrl.isEmpty()); } ///DUChain must be write-locked ///Also removes from the environment-manager if the top-context is not on disk void removeDocumentChainFromMemory(TopDUContext* context) { QMutexLocker l(&m_chainsMutex); { QMutexLocker l(&m_referenceCountsMutex); if(m_referenceCounts.contains(context)) { //This happens during shutdown, since everything is unloaded qCDebug(LANGUAGE) << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // qCDebug(LANGUAGE) << "duchain: removing document" << context->url().str(); Q_ASSERT(hasChainForIndex(index)); Q_ASSERT(m_chainsByUrl.contains(context->url(), context)); m_chainsByUrl.remove(context->url(), context); if(!context->isOnDisk()) instance->removeFromEnvironmentManager(context); l.unlock(); //DUChain is write-locked, so we can do whatever we want on the top-context, including deleting it context->deleteSelf(); l.relock(); Q_ASSERT(hasChainForIndex(index)); - SpinLock<> lock(DUChain::chainsByIndexLock); + QMutexLocker lock(&DUChain::chainsByIndexLock); DUChain::chainsByIndex[index] = 0; } ///Must be locked before accessing content of this class. ///Should be released during expensive disk-operations and such. QMutex m_chainsMutex; QMutex m_cleanupMutex; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; Definitions m_definitions; Uses m_uses; QSet m_loading; bool m_cleanupDisabled; //List of available top-context indices, protected by m_chainsMutex QVector m_availableTopContextIndices; ///Used to keep alive the top-context that belong to documents loaded in the editor QSet m_openDocumentContexts; bool m_destroyed; ///The item must not be stored yet ///m_chainsMutex should not be locked, since this can trigger I/O void addEnvironmentInformation(ParsingEnvironmentFilePointer info) { Q_ASSERT(!findInformation(info->indexedTopContext().index())); Q_ASSERT(m_environmentInfo.findIndex(info->indexedTopContext().index()) == 0); QMutexLocker lock(&m_chainsMutex); m_fileEnvironmentInformations.insert(info->url(), info); m_indexEnvironmentInformations.insert(info->indexedTopContext().index(), info); Q_ASSERT(info->d_func()->classId); } ///The item must be managed currently ///m_chainsMutex does not need to be locked void removeEnvironmentInformation(ParsingEnvironmentFilePointer info) { info->makeDynamic(); //By doing this, we make sure the data is actually being destroyed in the destructor bool removed = false; bool removed2 = false; { QMutexLocker lock(&m_chainsMutex); removed = m_fileEnvironmentInformations.remove(info->url(), info); removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); } { //Remove it from the environment information lists if it was there QMutexLocker lock(m_environmentListInfo.mutex()); uint index = m_environmentListInfo.findIndex(info->url()); if(index) { EnvironmentInformationListItem item(*m_environmentListInfo.itemFromIndex(index)); if(item.itemsList().removeOne(info->indexedTopContext().index())) { m_environmentListInfo.deleteItem(index); if(!item.itemsList().isEmpty()) m_environmentListInfo.index(EnvironmentInformationListRequest(info->url(), item)); } } } QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(info->indexedTopContext().index()); if(index) { m_environmentInfo.deleteItem(index); } Q_UNUSED(removed); Q_UNUSED(removed2); Q_ASSERT(index || (removed && removed2)); Q_ASSERT(!findInformation(info->indexedTopContext().index())); } ///m_chainsMutex should _not_ be locked, because this may trigger I/O QList getEnvironmentInformation(IndexedString url) { QList ret; uint listIndex = m_environmentListInfo.findIndex(url); if(listIndex) { KDevVarLengthArray topContextIndices; { //First store all the possible intices into the KDevVarLengthArray, so we can unlock the mutex before processing them. QMutexLocker lock(m_environmentListInfo.mutex()); //Lock the mutex to make sure the item isn't changed while it's being iterated const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(listIndex); FOREACH_FUNCTION(uint topContextIndex, item->items) topContextIndices << topContextIndex; } //Process the indices in a separate step after copying them from the array, so we don't need m_environmentListInfo.mutex locked, //and can call loadInformation(..) safely, which else might lead to a deadlock. FOREACH_ARRAY(uint topContextIndex, topContextIndices) { QExplicitlySharedDataPointer< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ qCDebug(LANGUAGE) << "Failed to load enviromment-information for" << TopDUContextDynamicData::loadUrl(topContextIndex).str(); } } } QMutexLocker l(&m_chainsMutex); //Add those information that have not been added to the stored lists yet foreach(ParsingEnvironmentFilePointer file, m_fileEnvironmentInformations.values(url)) if(!ret.contains(file)) ret << file; return ret; } ///Must be called _without_ the chainsByIndex spin-lock locked static inline bool hasChainForIndex(uint index) { - SpinLock<> lock(DUChain::chainsByIndexLock); + QMutexLocker lock(&DUChain::chainsByIndexLock); return (DUChain::chainsByIndex.size() > index) && DUChain::chainsByIndex[index]; } ///Must be called _without_ the chainsByIndex spin-lock locked. Returns the top-context if it is loaded. static inline TopDUContext* readChainForIndex(uint index) { - SpinLock<> lock(DUChain::chainsByIndexLock); + QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() > index) return DUChain::chainsByIndex[index]; else return 0; } ///Makes sure that the chain with the given index is loaded ///@warning m_chainsMutex must NOT be locked when this is called void loadChain(uint index, QSet& loaded) { QMutexLocker l(&m_chainsMutex); if(!hasChainForIndex(index)) { if(m_loading.contains(index)) { //It's probably being loaded by another thread. So wait until the load is ready while(m_loading.contains(index)) { l.unlock(); qCDebug(LANGUAGE) << "waiting for another thread to load index" << index; usleep(50000); l.relock(); } loaded.insert(index); return; } m_loading.insert(index); loaded.insert(index); l.unlock(); qCDebug(LANGUAGE) << "loading top-context" << index; TopDUContext* chain = TopDUContextDynamicData::load(index); if(chain) { chain->setParsingEnvironmentFile(loadInformation(chain->ownIndex())); if(!chain->usingImportsCache()) { //Eventually also load all the imported chains, so the import-structure is built foreach(const DUContext::Import &import, chain->DUContext::importedParentContexts()) { if(!loaded.contains(import.topContextIndex())) { loadChain(import.topContextIndex(), loaded); } } } chain->rebuildDynamicImportStructure(); chain->setInDuChain(true); instance->addDocumentChain(chain); } l.relock(); m_loading.remove(index); } } ///Stores all environment-information ///Also makes sure that all information that stays is referenced, so it stays alive. ///@param atomic If this is false, the write-lock will be released time by time void storeAllInformation(bool atomic, DUChainWriteLocker& locker) { uint cnt = 0; QList urls; { QMutexLocker lock(&m_chainsMutex); urls += m_fileEnvironmentInformations.keys(); } foreach(const IndexedString &url, urls) { QList check; { QMutexLocker lock(&m_chainsMutex); check = m_fileEnvironmentInformations.values(url); } foreach(ParsingEnvironmentFilePointer file, check) { EnvironmentInformationRequest req(file.data()); QMutexLocker lock(m_environmentInfo.mutex()); uint index = m_environmentInfo.findIndex(req); if(file->d_func()->isDynamic()) { //This item has been changed, or isn't in the repository yet //Eventually remove an old entry if(index) m_environmentInfo.deleteItem(index); //Add the new entry to the item repository index = m_environmentInfo.index(req); Q_ASSERT(index); EnvironmentInformationItem* item = const_cast(m_environmentInfo.itemFromIndex(index)); DUChainBaseData* theData = reinterpret_cast(reinterpret_cast(item) + sizeof(EnvironmentInformationItem)); Q_ASSERT(theData->m_range == file->d_func()->m_range); Q_ASSERT(theData->m_dynamic == false); Q_ASSERT(theData->classId == file->d_func()->classId); file->setData( theData ); ++cnt; }else{ m_environmentInfo.itemFromIndex(index); //Prevent unloading of the data, by accessing the item } } ///We must not release the lock while holding a reference to a ParsingEnvironmentFilePointer, else we may miss the deletion of an ///information, and will get crashes. if(!atomic && (cnt % 100 == 0)) { //Release the lock on a regular basis locker.unlock(); locker.lock(); } storeInformationList(url); //Access the data in the repository, so the bucket isn't unloaded uint index = m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)); if(index) { m_environmentListInfo.itemFromIndex(index); }else{ QMutexLocker lock(&m_chainsMutex); qCDebug(LANGUAGE) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { return m_cleanupMutex; } ///@param retries When this is nonzero, then doMoreCleanup will do the specified amount of cycles ///doing the cleanup without permanently locking the du-chain. During these steps the consistency ///of the disk-storage is not guaranteed, but only few changes will be done during these steps, ///so the final step where the duchain is permanently locked is much faster. void doMoreCleanup(int retries = 0, bool needLockRepository = true) { if(m_cleanupDisabled) return; //This mutex makes sure that there's never 2 threads at he same time trying to clean up QMutexLocker lockCleanupMutex(&cleanupMutex()); if(m_destroyed || m_cleanupDisabled) return; Q_ASSERT(!instance->lock()->currentThreadHasReadLock() && !instance->lock()->currentThreadHasWriteLock()); DUChainWriteLocker writeLock(instance->lock()); PersistentSymbolTable::self().clearCache(); //This is used to stop all parsing before starting to do the cleanup. This way less happens during the //soft cleanups, and we have a good chance that during the "hard" cleanup only few data has to be written. QList lockedParseMutexes; QList locked; if(needLockRepository) { if (ICore* core = ICore::self()) if (ILanguageController* lc = core->languageController()) lockedParseMutexes = lc->loadedLanguages(); writeLock.unlock(); //Here we wait for all parsing-threads to stop their processing foreach(ILanguage* language, lockedParseMutexes) { language->parseLock()->lockForWrite(); locked << language->parseLock(); } writeLock.lock(); globalItemRepositoryRegistry().lockForWriting(); qCDebug(LANGUAGE) << "starting cleanup"; } QTime startTime = QTime::currentTime(); storeAllInformation(!retries, writeLock); //Puts environment-information into a repository //We don't need to increase the reference-count, since the cleanup-mutex is locked QSet workOnContexts; { QMutexLocker l(&m_chainsMutex); foreach(TopDUContext* top, m_chainsByUrl.values()) { workOnContexts << top; Q_ASSERT(hasChainForIndex(top->ownIndex())); } } foreach(TopDUContext* context, workOnContexts) { context->m_dynamicData->store(); if(retries) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between usleep(500); writeLock.lock(); } } //Unload all top-contexts that don't have a reference-count and that are not imported by a referenced one QSet unloadedNames; bool unloadedOne = true; bool unloadAllUnreferenced = !retries; //Now unload contexts, but only ones that are not imported by any other currently loaded context //The complication: Since during the lock-break new references may be added, we must never keep //the du-chain in an invalid state. Thus we can only unload contexts that are not imported by any //currently loaded contexts. In case of loops, we have to unload everything at once. while(unloadedOne) { unloadedOne = false; int hadUnloadable = 0; unloadContexts: foreach(TopDUContext* unload, workOnContexts) { bool hasReference = false; { QMutexLocker l(&m_referenceCountsMutex); //Test if the context is imported by a referenced one foreach(TopDUContext* context, m_referenceCounts.keys()) { if(context == unload || context->imports(unload, CursorInRevision())) { workOnContexts.remove(unload); hasReference = true; } } } if(!hasReference) ++hadUnloadable; //We have found a context that is not referenced else continue; //This context is referenced bool isImportedByLoaded = !unload->loadedImporters().isEmpty(); //If we unload a context that is imported by other contexts, we create a bad loaded state if(isImportedByLoaded && !unloadAllUnreferenced) continue; unloadedNames.insert(unload->url()); //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 //If nothing has changed, it is only a low-cost call. unload->m_dynamicData->store(); Q_ASSERT(!unload->d_func()->m_dynamic); removeDocumentChainFromMemory(unload); workOnContexts.remove(unload); unloadedOne = true; if(!unloadAllUnreferenced) { //Eventually give other threads a chance to access the duchain writeLock.unlock(); //Sleep to give the other threads a realistic chance to get a read-lock in between usleep(500); writeLock.lock(); } } if(hadUnloadable && !unloadedOne) { Q_ASSERT(!unloadAllUnreferenced); //This can happen in case of loops. We have o unload everything at one time. qCDebug(LANGUAGE) << "found" << hadUnloadable << "unloadable contexts, but could not unload separately. Unloading atomically."; unloadAllUnreferenced = true; hadUnloadable = 0; //Reset to 0, so we cannot loop forever goto unloadContexts; } } if(retries == 0) { QMutexLocker lock(&m_chainsMutex); //Do this atomically, since we must be sure that _everything_ is already saved for(QMultiMap::iterator it = m_fileEnvironmentInformations.begin(); it != m_fileEnvironmentInformations.end(); ) { ParsingEnvironmentFile* f = it->data(); Q_ASSERT(f->d_func()->classId); if(f->ref.load() == 1) { Q_ASSERT(!f->d_func()->isDynamic()); //It cannot be dynamic, since we have stored before //The ParsingEnvironmentFilePointer is only referenced once. This means that it does not belong to any //loaded top-context, so just remove it to save some memory and processing time. ///@todo use some kind of timeout before removing it = m_fileEnvironmentInformations.erase(it); }else{ ++it; } } } if(retries) writeLock.unlock(); //This must be the last step, due to the on-disk reference counting globalItemRepositoryRegistry().store(); //Stores all repositories { //Store the static parsing-environment file data ///@todo Solve this more elegantly, using a general mechanism to store static duchain-like data Q_ASSERT(ParsingEnvironmentFile::m_staticData); QFile f(globalItemRepositoryRegistry().path() + "/parsing_environment_data"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); } ///Write out the list of available top-context indices { QMutexLocker lock(&m_chainsMutex); QFile f(globalItemRepositoryRegistry().path() + "/available_top_context_indices"); bool opened = f.open(QIODevice::WriteOnly); Q_ASSERT(opened); Q_UNUSED(opened); f.write((char*)m_availableTopContextIndices.data(), m_availableTopContextIndices.size() * sizeof(uint)); } if(retries) { doMoreCleanup(retries-1, false); writeLock.lock(); } if(needLockRepository) { globalItemRepositoryRegistry().unlockForWriting(); int elapsedSeconds = startTime.secsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); qCDebug(LANGUAGE) << "milliseconds spent doing cleanup with locked duchain: " << elapesedMilliSeconds; } foreach(QReadWriteLock* lock, locked) lock->unlock(); } ///Checks whether the information is already loaded. ParsingEnvironmentFile* findInformation(uint topContextIndex) { QMutexLocker lock(&m_chainsMutex); QHash::iterator it = m_indexEnvironmentInformations.find(topContextIndex); if(it != m_indexEnvironmentInformations.end()) return (*it).data(); return 0; } ///Loads/gets the environment-information for the given top-context index, or returns zero if none exists ///@warning m_chainsMutex should NOT be locked when this is called, because it triggers I/O ///@warning no other mutexes should be locked, as that may lead to a dedalock ParsingEnvironmentFile* loadInformation(uint topContextIndex) { ParsingEnvironmentFile* alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; //Step two: Check if it is on disk, and if is, load it uint dataIndex = m_environmentInfo.findIndex(EnvironmentInformationRequest(topContextIndex)); if(!dataIndex) { //No environment-information stored for this top-context return 0; } const EnvironmentInformationItem& item(*m_environmentInfo.itemFromIndex(dataIndex)); QMutexLocker lock(&m_chainsMutex); //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 alreadyLoaded = findInformation(topContextIndex); if(alreadyLoaded) return alreadyLoaded; ///FIXME: ugly, and remove const_cast ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( const_cast(reinterpret_cast(reinterpret_cast(&item) + sizeof(EnvironmentInformationItem))) )); if(ret) { Q_ASSERT(ret->d_func()->classId); Q_ASSERT(ret->indexedTopContext().index() == topContextIndex); ParsingEnvironmentFilePointer retPtr(ret); m_fileEnvironmentInformations.insert(ret->url(), retPtr); Q_ASSERT(!m_indexEnvironmentInformations.contains(ret->indexedTopContext().index())); m_indexEnvironmentInformations.insert(ret->indexedTopContext().index(), retPtr); } return ret; } struct CleanupListVisitor { QList checkContexts; bool operator()(const EnvironmentInformationItem* item) { checkContexts << item->m_topContext; return true; } }; ///Will check a selection of all top-contexts for up-to-date ness, and remove them if out of date void cleanupTopContexts() { DUChainWriteLocker lock( DUChain::lock() ); qCDebug(LANGUAGE) << "cleaning top-contexts"; CleanupListVisitor visitor; uint startPos = 0; m_environmentInfo.visitAllItems(visitor); int checkContextsCount = maxFinalCleanupCheckContexts; int percentageOfContexts = (visitor.checkContexts.size() * 100) / minimumFinalCleanupCheckContextsPercentage; if(checkContextsCount < percentageOfContexts) checkContextsCount = percentageOfContexts; if(visitor.checkContexts.size() > (int)checkContextsCount) startPos = qrand() % (visitor.checkContexts.size() - checkContextsCount); int endPos = startPos + maxFinalCleanupCheckContexts; if(endPos > visitor.checkContexts.size()) endPos = visitor.checkContexts.size(); QSet< uint > check; for(int a = startPos; a < endPos && check.size() < checkContextsCount; ++a) if(check.size() < checkContextsCount) addContextsForRemoval(check, IndexedTopDUContext(visitor.checkContexts[a])); foreach(uint topIndex, check) { IndexedTopDUContext top(topIndex); if(top.data()) { qCDebug(LANGUAGE) << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } qCDebug(LANGUAGE) << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; QExplicitlySharedDataPointer info( instance->environmentFileForDocument(top) ); ///@todo Also check if the context is "useful"(Not a duplicate context, imported by a useful one, ...) if(info && info->needsUpdate()) { //This context will be removed }else{ return; } topContexts.insert(top.index()); if(info) { //Check whether importers need to be removed as well QList< QExplicitlySharedDataPointer > importers = info->importers(); QSet< QExplicitlySharedDataPointer > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< QExplicitlySharedDataPointer >::iterator it = importers.begin(); it != importers.end(); ++it) { IndexedTopDUContext c = (*it)->indexedTopContext(); if(!topContexts.contains(c.index())) { topContexts.insert(c.index()); //Prevent useless recursion checkNext.insert(*it); } } for(QSet< QExplicitlySharedDataPointer >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) { topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again addContextsForRemoval(topContexts, (*it)->indexedTopContext()); } } } template bool listContains(const Entry entry, const Entry* list, uint listSize) { for(uint a = 0; a < listSize; ++a) if(list[a] == entry) return true; return false; } ///Stores the environment-information for the given url void storeInformationList(IndexedString url) { QMutexLocker lock(m_environmentListInfo.mutex()); EnvironmentInformationListItem newItem; newItem.m_file = url; QSet newItems; { QMutexLocker lock(&m_chainsMutex); QMultiMap::iterator start = m_fileEnvironmentInformations.lowerBound(url); QMultiMap::iterator end = m_fileEnvironmentInformations.upperBound(url); for(QMultiMap::iterator it = start; it != end; ++it) { uint topContextIndex = (*it)->indexedTopContext().index(); newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } uint index = m_environmentListInfo.findIndex(url); if(index) { //We only handle adding items here, since we can never be sure whether everything is loaded //Removal is handled directly in removeEnvironmentInformation const EnvironmentInformationListItem* item = m_environmentListInfo.itemFromIndex(index); QSet oldItems; FOREACH_FUNCTION(uint topContextIndex, item->items) { oldItems.insert(topContextIndex); if(!newItems.contains(topContextIndex)) { newItems.insert(topContextIndex); newItem.itemsList().append(topContextIndex); } } if(oldItems == newItems) return; ///Update/insert a new list m_environmentListInfo.deleteItem(index); //Remove the previous item } Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url)) == 0); //Insert the new item m_environmentListInfo.index(EnvironmentInformationListRequest(url, newItem)); Q_ASSERT(m_environmentListInfo.findIndex(EnvironmentInformationListRequest(url))); } //Loaded environment-informations. Protected by m_chainsMutex QMultiMap m_fileEnvironmentInformations; QHash m_indexEnvironmentInformations; ///The following repositories are thread-safe, and m_chainsMutex should not be locked when using them, because ///they may trigger I/O. Still it may be required to lock their local mutexes. ///Maps filenames to a list of top-contexts/environment-information. ItemRepository m_environmentListInfo; ///Maps top-context-indices to environment-information item. ItemRepository m_environmentInfo; }; Q_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { Q_ASSERT(ICore::self()); connect(ICore::self()->documentController(), &IDocumentController::documentLoadedPrepare, this, &DUChain::documentLoadedPrepare); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &DUChain::documentRenamed); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &DUChain::documentActivated); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &DUChain::documentClosed); } DUChain::~DUChain() { DUChain::m_deleted = true; } DUChain* DUChain::self() { return sdDUChainPrivate->instance; } extern void initModificationRevisionSetRepository(); extern void initDeclarationRepositories(); extern void initIdentifierRepository(); extern void initTypeRepository(); extern void initInstantiationInformationRepository(); extern void initReferenceCounting(); void DUChain::initialize() { // Initialize the global item repository as first thing after loading the session Q_ASSERT(ICore::self()); Q_ASSERT(ICore::self()->activeSession()); ItemRepositoryRegistry::initialize(ICore::self()->activeSessionLock()); initReferenceCounting(); // This needs to be initialized here too as the function is not threadsafe, but can // sometimes be called from different threads. This results in the underlying QFile // being 0 and hence crashes at some point later when accessing the contents via // read. See https://bugs.kde.org/show_bug.cgi?id=250779 RecursiveImportRepository::repository(); RecursiveImportCacheRepository::repository(); // similar to above, see https://bugs.kde.org/show_bug.cgi?id=255323 initDeclarationRepositories(); initModificationRevisionSetRepository(); initIdentifierRepository(); initTypeRepository(); initInstantiationInformationRepository(); Importers::self(); globalImportIdentifier(); globalIndexedImportIdentifier(); globalAliasIdentifier(); globalIndexedAliasIdentifier(); } DUChainLock* DUChain::lock() { return &sdDUChainPrivate->lock; } QList DUChain::allChains() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); return sdDUChainPrivate->m_chainsByUrl.values(); } void DUChain::updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); removeFromEnvironmentManager( context ); context->setParsingEnvironmentFile( file ); addToEnvironmentManager( context ); } void DUChain::removeDocumentChain( TopDUContext* context ) { ENSURE_CHAIN_WRITE_LOCKED; IndexedTopDUContext indexed(context->indexed()); Q_ASSERT(indexed.data() == context); ///This assertion fails if you call removeDocumentChain(..) on a document that has not been added to the du-chain context->m_dynamicData->deleteOnDisk(); Q_ASSERT(indexed.data() == context); sdDUChainPrivate->removeDocumentChainFromMemory(context); Q_ASSERT(!indexed.data()); Q_ASSERT(!environmentFileForDocument(indexed)); QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); sdDUChainPrivate->m_availableTopContextIndices.push_back(indexed.index()); } void DUChain::addDocumentChain( TopDUContext * chain ) { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // qCDebug(LANGUAGE) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { - SpinLock<> lock(DUChain::chainsByIndexLock); + QMutexLocker lock(&DUChain::chainsByIndexLock); if(DUChain::chainsByIndex.size() <= chain->ownIndex()) DUChain::chainsByIndex.resize(chain->ownIndex() + 100, 0); DUChain::chainsByIndex[chain->ownIndex()] = chain; } { Q_ASSERT(DUChain::chainsByIndex[chain->ownIndex()]); } Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); sdDUChainPrivate->m_chainsByUrl.insert(chain->url(), chain); Q_ASSERT(sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); chain->setInDuChain(true); l.unlock(); addToEnvironmentManager(chain); // This function might be called during shutdown by stale parse jobs // Make sure we don't access null-pointers here if (ICore::self() && ICore::self()->languageController() && ICore::self()->languageController()->backgroundParser()->trackerForUrl(chain->url())) { //Make sure the context stays alive at least as long as the context is open ReferencedTopDUContext ctx(chain); sdDUChainPrivate->m_openDocumentContexts.insert(ctx); } } void DUChain::addToEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage Q_ASSERT(file->indexedTopContext().index() == chain->ownIndex()); if(ParsingEnvironmentFile* alreadyHave = sdDUChainPrivate->findInformation(file->indexedTopContext().index())) { ///If this triggers, there has already been another environment-information registered for this top-context. ///removeFromEnvironmentManager should have been called before to remove the old environment-information. Q_ASSERT(alreadyHave == file.data()); Q_UNUSED(alreadyHave); return; } sdDUChainPrivate->addEnvironmentInformation(file); } void DUChain::removeFromEnvironmentManager( TopDUContext * chain ) { ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); if( !file ) return; //We don't need to manage sdDUChainPrivate->removeEnvironmentInformation(file); } TopDUContext* DUChain::chainForDocument(const QUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document), proxyContext); } bool DUChain::isInMemory(uint topContextIndex) const { return DUChainPrivate::hasChainForIndex(topContextIndex); } IndexedString DUChain::urlForIndex(uint index) const { { TopDUContext* chain = DUChainPrivate::readChainForIndex(index); if(chain) return chain->url(); } return TopDUContextDynamicData::loadUrl(index); } TopDUContext* DUChain::loadChain(uint index) { QSet loaded; sdDUChainPrivate->loadChain(index, loaded); { - SpinLock<> lock(chainsByIndexLock); + QMutexLocker lock(&chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } return 0; } TopDUContext* DUChain::chainForDocument(const KDevelop::IndexedString& document, bool proxyContext) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return 0; QList list = sdDUChainPrivate->getEnvironmentInformation(document); foreach(const ParsingEnvironmentFilePointer &file, list) if(isInMemory(file->indexedTopContext().index()) && file->isProxyContext() == proxyContext) { return file->topContext(); } foreach(const ParsingEnvironmentFilePointer &file, list) if(proxyContext == file->isProxyContext()) { return file->topContext(); } //Allow selecting a top-context even if there is no ParsingEnvironmentFile QList< TopDUContext* > ret = chainsForDocument(document); foreach(TopDUContext* ctx, ret) { if(!ctx->parsingEnvironmentFile() || (ctx->parsingEnvironmentFile()->isProxyContext() == proxyContext)) return ctx; } return 0; } QList DUChain::chainsForDocument(const QUrl& document) const { return chainsForDocument(IndexedString(document)); } QList DUChain::chainsForDocument(const IndexedString& document) const { QList chains; if(sdDUChainPrivate->m_destroyed) return chains; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); // Match all parsed versions of this document for (auto it = sdDUChainPrivate->m_chainsByUrl.lowerBound(document); it != sdDUChainPrivate->m_chainsByUrl.end(); ++it) { if (it.key() == document) chains << it.value(); else break; } return chains; } TopDUContext* DUChain::chainForDocument( const QUrl& document, const KDevelop::ParsingEnvironment* environment, bool proxyContext ) const { return chainForDocument( IndexedString(document), environment, proxyContext ); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { ENSURE_CHAIN_READ_LOCKED; if(sdDUChainPrivate->m_destroyed) return ParsingEnvironmentFilePointer(); QList< ParsingEnvironmentFilePointer> list = sdDUChainPrivate->getEnvironmentInformation(document); // qCDebug(LANGUAGE) << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); auto it = list.constBegin(); while(it != list.constEnd()) { if(*it && ((*it)->isProxyContext() == proxyContext) && (*it)->matchEnvironment(environment) && // Verify that the environment-file and its top-context are "good": The top-context must exist, // and there must be a content-context associated to the proxy-context. (*it)->topContext() && (!proxyContext || DUChainUtils::contentContextFromProxyContext((*it)->topContext())) ) { return *it; } ++it; } return ParsingEnvironmentFilePointer(); } QList DUChain::allEnvironmentFiles(const IndexedString& document) { return sdDUChainPrivate->getEnvironmentInformation(document); } ParsingEnvironmentFilePointer DUChain::environmentFileForDocument(IndexedTopDUContext topContext) const { if(topContext.index() == 0) return ParsingEnvironmentFilePointer(); return ParsingEnvironmentFilePointer(sdDUChainPrivate->loadInformation(topContext.index())); } TopDUContext* DUChain::chainForDocument( const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext ) const { if(sdDUChainPrivate->m_destroyed) return 0; ParsingEnvironmentFilePointer envFile = environmentFileForDocument(document, environment, proxyContext); if(envFile) { return envFile->topContext(); }else{ return 0; } } QList DUChain::documents() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url().toUrl(); } return ret; } QList DUChain::indexedDocuments() const { QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); QList ret; foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) { ret << top->url(); } return ret; } void DUChain::documentActivated(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; //Check whether the document has an attached environment-manager, and whether that one thinks the document needs to be updated. //If yes, update it. DUChainReadLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* ctx = DUChainUtils::standardContextForUrl(doc->url(), true); if(ctx && ctx->parsingEnvironmentFile()) if(ctx->parsingEnvironmentFile()->needsUpdate()) ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url())); } void DUChain::documentClosed(IDocument* document) { if(sdDUChainPrivate->m_destroyed) return; IndexedString url(document->url()); foreach(const ReferencedTopDUContext &top, sdDUChainPrivate->m_openDocumentContexts) if(top->url() == url) sdDUChainPrivate->m_openDocumentContexts.remove(top); } void DUChain::documentLoadedPrepare(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; const IndexedString url(doc->url()); DUChainWriteLocker lock( DUChain::lock() ); QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(doc->url()); QList chains = chainsForDocument(url); QList languages = ICore::self()->languageController()->languagesForUrl(doc->url()); if(standardContext) { Q_ASSERT(chains.contains(standardContext)); //We have just loaded it Q_ASSERT((standardContext->url() == url)); sdDUChainPrivate->m_openDocumentContexts.insert(standardContext); bool needsUpdate = standardContext->parsingEnvironmentFile() && standardContext->parsingEnvironmentFile()->needsUpdate(); if(!needsUpdate) { //Only apply the highlighting if we don't need to update, else we might highlight total crap //Do instant highlighting only if all imports are loaded, to make sure that we don't block the user-interface too long //Else the highlighting will be done in the background-thread //This is not exactly right, as the direct imports don't necessarily equal the real imports used by uses //but it approximates the correct behavior. bool allImportsLoaded = true; foreach(const DUContext::Import& import, standardContext->importedParentContexts()) if(!import.indexedContext().indexedTopContext().isLoaded()) allImportsLoaded = false; if(allImportsLoaded) { l.unlock(); lock.unlock(); foreach( KDevelop::ILanguage* language, languages) if(language->languageSupport() && language->languageSupport()->codeHighlighting()) language->languageSupport()->codeHighlighting()->highlightDUChain(standardContext); qCDebug(LANGUAGE) << "highlighted" << doc->url() << "in foreground"; return; } }else{ qCDebug(LANGUAGE) << "not highlighting the duchain because the documents needs an update"; } if(needsUpdate || !(standardContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); return; } } //Add for highlighting etc. ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), TopDUContext::AllDeclarationsContextsAndUses); } void DUChain::documentRenamed(KDevelop::IDocument* doc) { if(sdDUChainPrivate->m_destroyed) return; if(!doc->url().isValid()) { ///Maybe this happens when a file was deleted? qCWarning(LANGUAGE) << "Strange, url of renamed document is invalid!"; }else{ ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(doc->url()), (TopDUContext::Features)(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate)); } } Uses* DUChain::uses() { return &sdDUChainPrivate->m_uses; } Definitions* DUChain::definitions() { return &sdDUChainPrivate->m_definitions; } static void finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); qCDebug(LANGUAGE) << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { qCDebug(LANGUAGE) << "cleaned" << cleaned << "B"; if(cleaned < 1000) { qCDebug(LANGUAGE) << "cleaned enough"; break; } } qCDebug(LANGUAGE) << "final cleanup ready"; } void DUChain::shutdown() { // if core is not shutting down, we can end up in deadlocks or crashes // since language plugins might still try to access static duchain stuff Q_ASSERT(!ICore::self() || ICore::self()->shuttingDown()); qCDebug(LANGUAGE) << "Cleaning up and shutting down DUChain"; QMutexLocker lock(&sdDUChainPrivate->cleanupMutex()); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); sdDUChainPrivate->cleanupTopContexts(); globalItemRepositoryRegistry().unlockForWriting(); } sdDUChainPrivate->doMoreCleanup(); //Must be done _before_ finalCleanup, else we may be deleting yet needed data sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); { //Acquire write-lock of the repository, so when kdevelop crashes in that process, the repository is discarded //Crashes here may happen in an inconsistent state, thus this makes sense, to protect the user from more crashes globalItemRepositoryRegistry().lockForWriting(); finalCleanup(); globalItemRepositoryRegistry().unlockForWriting(); } globalItemRepositoryRegistry().shutdown(); } uint DUChain::newTopContextIndex() { { QMutexLocker lock(&sdDUChainPrivate->m_chainsMutex); if(!sdDUChainPrivate->m_availableTopContextIndices.isEmpty()) { uint ret = sdDUChainPrivate->m_availableTopContextIndices.back(); sdDUChainPrivate->m_availableTopContextIndices.pop_back(); if(TopDUContextDynamicData::fileExists(ret)) { qCWarning(LANGUAGE) << "Problem in the management of availalbe top-context indices"; return newTopContextIndex(); } return ret; } } static QAtomicInt& currentId( globalItemRepositoryRegistry().getCustomCounter("Top-Context Counter", 1) ); return currentId.fetchAndAddRelaxed(1); } void DUChain::refCountUp(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) sdDUChainPrivate->m_referenceCounts.insert(top, 1); else ++sdDUChainPrivate->m_referenceCounts[top]; } bool DUChain::deleted() { return m_deleted; } void DUChain::refCountDown(TopDUContext* top) { QMutexLocker l(&sdDUChainPrivate->m_referenceCountsMutex); if(!sdDUChainPrivate->m_referenceCounts.contains(top)) { //qCWarning(LANGUAGE) << "tried to decrease reference-count for" << top->url().str() << "but this top-context is not referenced"; return; } --sdDUChainPrivate->m_referenceCounts[top]; if(!sdDUChainPrivate->m_referenceCounts[top]) sdDUChainPrivate->m_referenceCounts.remove(top); } void DUChain::emitDeclarationSelected(const DeclarationPointer& decl) { emit declarationSelected(decl); } KDevelop::ReferencedTopDUContext DUChain::waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext) { Q_ASSERT(!lock()->currentThreadHasReadLock() && !lock()->currentThreadHasWriteLock()); WaitForUpdate waiter; updateContextForUrl(document, minFeatures, &waiter); // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { // we might have been shut down in the meanwhile if (!ICore::self()) { return 0; } QMetaObject::invokeMethod(ICore::self()->languageController()->backgroundParser(), "parseDocuments"); QApplication::processEvents(); usleep(1000); } if(!proxyContext) { DUChainReadLocker readLock(DUChain::lock()); return DUChainUtils::contentContextFromProxyContext(waiter.m_topContext); } return waiter.m_topContext; } void DUChain::updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady, int priority) const { DUChainReadLocker lock( DUChain::lock() ); TopDUContext* standardContext = DUChainUtils::standardContextForUrl(document.toUrl()); if(standardContext && standardContext->parsingEnvironmentFile() && !standardContext->parsingEnvironmentFile()->needsUpdate() && standardContext->parsingEnvironmentFile()->featuresSatisfied(minFeatures)) { lock.unlock(); if(notifyReady) QMetaObject::invokeMethod(notifyReady, "updateReady", Qt::DirectConnection, Q_ARG(KDevelop::IndexedString, document), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext(standardContext))); }else{ ///Start a parse-job for the given document ICore::self()->languageController()->backgroundParser()->addDocument(document, minFeatures, priority, notifyReady); } } void DUChain::disablePersistentStorage(bool disable) { sdDUChainPrivate->m_cleanupDisabled = disable; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } diff --git a/language/duchain/duchain.h b/language/duchain/duchain.h index 9180cf13e9..5c38f243f1 100644 --- a/language/duchain/duchain.h +++ b/language/duchain/duchain.h @@ -1,301 +1,301 @@ /* This file is part of KDevelop Copyright 2006-2008 Hamish Rodda Copyright 2007-2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_DUCHAIN_H #define KDEVPLATFORM_DUCHAIN_H #include #include "topducontext.h" #include "parsingenvironment.h" class QUrl; namespace KDevelop { class IDocument; class TopDUContext; class DUChainLock; class ParsingEnvironmentManager; class ParsingEnvironment; class ParsingEnvironmentFile; typedef QExplicitlySharedDataPointer ParsingEnvironmentFilePointer; class Definitions; class Uses; /** * \short Holds references to all top level source file contexts. * * The DUChain is a global static class which manages the definition-use * chains. It performs the following functions: * \li registers chains with addDocumentChain() and deregisters with removeDocumentChain() * \li allows querying for existing chains * \li watches text editors, registering and deregistering them with the BackgroundParser when files * are opened and closed. */ class KDEVPLATFORMLANGUAGE_EXPORT DUChain : public QObject { Q_OBJECT public: /** * Initializes common static item repositories. * Must be called once for multi threaded applications to work reliably. */ static void initialize(); /** * Return a list of all chains available */ Q_SCRIPTABLE QList allChains() const; /** * Makes sure the standard-context for the given url is up-to-date. * This may trigger a parsing in background, so a QObject can be given that will be notified * asyonchronously once the update is ready. * If the context is already up to date, the given QObject is notified directly. * * @param document Document to update * @param features The requested features. If you want to force a full update of the context, give TopDUContext::ForceUpdate. * If you want to force an update including all imports, use TopDUContext::ForceUpdateRecursive. * @param notifyReady An optional pointer to a QObject that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once for each call to updateContextForUrl. The given top-context * may be invalid if the update failed. A queued connection is used if a re-parse has to be done. The duchain * will _not_ be locked when updateReady is called. * @param priority An optional priority for the job. The lower the value, the higher it's priority. * @note The duchain must _not_ be locked when this is called! */ Q_SCRIPTABLE void updateContextForUrl(const IndexedString& document, TopDUContext::Features minFeatures, QObject* notifyReady = 0, int priority = 1) const; /** * Convenience-function similar to updateContextForUrl that blocks this thread until the update of the given document is ready, * and returns the top-context. * @param document The document to update * @param features The requested features. If you want to force a full update of the context, give TopDUContext::ForceUpdate. * If you want to force an update including all imports, use TopDUContext::ForceUpdateRecursive. * @return The up-to-date top-context, or zero if the update failed * * @note The duchain must _not_ be locked when this is called! * */ KDevelop::ReferencedTopDUContext waitForUpdate(const KDevelop::IndexedString& document, KDevelop::TopDUContext::Features minFeatures, bool proxyContext = false); /** * Return any chain for the given document * If available, the version accepting IndexedString should be used instead of this, for performance reasons. * When no fitting chain is in memory, one may be loaded from disk. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE TopDUContext* chainForDocument(const QUrl& document, bool proxyContext = false) const; Q_SCRIPTABLE TopDUContext* chainForDocument(const IndexedString& document, bool proxyContext = false) const; /** * Return all chains for the given document that are currently in memory. * This does not load any chains from disk. * */ Q_SCRIPTABLE QList chainsForDocument(const QUrl& document) const; /** * Return all chains for the given document that are currently in memory. * This does not load any chains from disk. * Should be preferred over the QUrl version. * */ Q_SCRIPTABLE QList chainsForDocument(const IndexedString& document) const; /** * Find a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE TopDUContext* chainForDocument(const QUrl& document, const ParsingEnvironment* environment, bool proxyContext = false) const; /** * Find a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * Prefer this over the QUrl version. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE TopDUContext* chainForDocument(const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext = false) const; /** * Find the environment-file of a chain that fits into the given environment. If no fitting chain is found, 0 is returned. * When no fitting chain is in memory, one may be loaded from disk. * * This should be preferred over chainForDocument when only the environment-info is needed, because the TopDUContext is not loaded in this function. * ** @param proxyContext If this is true, only contexts are found that have an ParsingEnvironmentFile that has the proxy-flag set. Else, only content-contexts will be returned. * * Prefer this over the QUrl version. * * @note The duchain must be at least read-locked locked when this is called! * */ Q_SCRIPTABLE ParsingEnvironmentFilePointer environmentFileForDocument(const IndexedString& document, const ParsingEnvironment* environment, bool proxyContext = false) const; Q_SCRIPTABLE ParsingEnvironmentFilePointer environmentFileForDocument(IndexedTopDUContext topContext) const; /** * Returns the list of the environment-infos of all versions of the given document. */ Q_SCRIPTABLE QList allEnvironmentFiles(const IndexedString& document); ///Returns the top-context that has the given index assigned, or zero if it doesn't exist. @see TopDUContext::ownIndex ///The duchain must be read-locked when this is called ///This function is inlined because it is called in a very high frequency Q_SCRIPTABLE inline TopDUContext* chainForIndex(uint index) { if(m_deleted) return 0; { - SpinLock<> lock(chainsByIndexLock); + QMutexLocker lock(&chainsByIndexLock); if(chainsByIndex.size() > index) { TopDUContext* top = chainsByIndex[index]; if(top) return top; } } //Load the top-context return loadChain(index); } ///Returns the url for the given top-context index if available. This does have some cost, so avoid it when possible. Q_SCRIPTABLE IndexedString urlForIndex(uint index) const; /// Only used for debugging at the moment Q_SCRIPTABLE QList documents() const; /// Only used for debugging at the moment /// Prefer that over the QUrl version for performance reasons Q_SCRIPTABLE QList indexedDocuments() const; /** * Registers a new definition-use \a chain for the given \a document. */ Q_SCRIPTABLE void addDocumentChain(TopDUContext* chain); /// Returns true if the global duchain instance has already been deleted Q_SCRIPTABLE static bool deleted(); /// Returns the global static instance. Q_SCRIPTABLE static DUChain* self(); /// Returns the structure that manages mapping between definitions and declarations Q_SCRIPTABLE static Definitions* definitions(); /// Returns the structure that manages mapping between declarations, and which top level contexts contain uses of them. static Uses* uses(); /** * Retrieve the read write lock for the entire definition-use chain. * To call non-const methods, you must be holding a write lock. * * Evaluations made prior to holding a lock (including which objects * exist) must be verified once the lock is held, as they may have changed * or been deleted. * * \threadsafe */ Q_SCRIPTABLE static DUChainLock* lock(); /// Returns whether the top-context with the given index is currently loaded in memory Q_SCRIPTABLE bool isInMemory(uint topContextIndex) const; /** * Changes the environment attached to the given top-level context, and updates the management-structures to reflect that * */ Q_SCRIPTABLE void updateContextEnvironment( TopDUContext* context, ParsingEnvironmentFile* file ); ///Allocates a new identity for a new top-context, no lock needed. The returned value is never zero static uint newTopContextIndex(); ///If you call this, the persistent disk-storage structure will stay unaffected, and no duchain cleanup will be done. ///Call this from within tests. void disablePersistentStorage(bool disable = true); ///Stores the whole duchain and all its repositories in the current state to disk ///The duchain must not be locked in any way void storeToDisk(); ///Compares the whole duchain and all its repositories in the current state to disk ///When the comparison fails, debug-output will show why ///The duchain must not be locked when calling this ///@return true If the current memory state equals the disk state, else false bool compareToDisk(); Q_SIGNALS: ///Is emitted when the declaration has been selected somewhere in the user-interface, for example in the completion-list void declarationSelected(const KDevelop::DeclarationPointer& decl); public Q_SLOTS: ///Removes the given top-context from the duchain, and deletes it. void removeDocumentChain(KDevelop::TopDUContext* document); ///Emits the declarationSelected signal, so other parties can notice it. void emitDeclarationSelected(const KDevelop::DeclarationPointer& decl); /** * Shutdown and cleanup the DUChain. */ void shutdown(); private Q_SLOTS: void documentActivated(KDevelop::IDocument* doc); void documentLoadedPrepare(KDevelop::IDocument* document); void documentRenamed(KDevelop::IDocument* document); void documentClosed(KDevelop::IDocument*); private: TopDUContext* loadChain(uint index); //These two are exported here so that the extremely frequently called chainForIndex(..) can be inlined static bool m_deleted; static std::vector chainsByIndex; - static SpinLockData chainsByIndexLock; + static QMutex chainsByIndexLock; /// Increases the reference-count for the given top-context. The result: It will not be unloaded. /// Do this to prevent KDevelop from unloading a top-context that you plan to use. Don't forget calling unReferenceToContext again, /// else the top-context will stay in memory for ever. void refCountUp(TopDUContext* top); /// Decreases the reference-count for the given top-context. When it reaches zero, KDevelop is free to unload it at any time, /// also invalidating all the contained declarations and contexts. void refCountDown(TopDUContext* top); void addToEnvironmentManager( TopDUContext * chain ); void removeFromEnvironmentManager( TopDUContext * chain ); DUChain(); ~DUChain(); friend class DUChainPrivate; friend class ReferencedTopDUContext; }; } #endif // KDEVPLATFORM_DUCHAIN_H diff --git a/serialization/referencecounting.cpp b/serialization/referencecounting.cpp index 6091c4003f..30c84f2dfe 100644 --- a/serialization/referencecounting.cpp +++ b/serialization/referencecounting.cpp @@ -1,261 +1,260 @@ /* * This file is part of KDevelop * * Copyright 2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "referencecounting.h" #include #include #include #include "serialization/itemrepository.h" -#include namespace KDevelop { bool doReferenceCounting = false; //Protects the reference-counting data through a spin-lock - SpinLockData refCountingLock; + QMutex refCountingLock; QMap >* refCountingRanges = new QMap >(); //ptr, , leaked intentionally! bool refCountingHasAdditionalRanges = false; //Whether 'refCountingRanges' is non-empty //Speedup: In most cases there is only exactly one reference-counted range active, //so the first reference-counting range can be marked here. void* refCountingFirstRangeStart = 0; QPair refCountingFirstRangeExtent = qMakePair(0u, 0u); } void KDevelop::disableDUChainReferenceCounting(void* start) { - SpinLock<> lock(refCountingLock); + QMutexLocker lock(&refCountingLock); if(refCountingFirstRangeStart && ((char*)refCountingFirstRangeStart) <= (char*)start && (char*)start < ((char*)refCountingFirstRangeStart) + refCountingFirstRangeExtent.first) { Q_ASSERT(refCountingFirstRangeExtent.second > 0); --refCountingFirstRangeExtent.second; if(refCountingFirstRangeExtent.second == 0) { refCountingFirstRangeExtent = qMakePair(0, 0); refCountingFirstRangeStart = 0; } } else if(refCountingHasAdditionalRanges) { QMap< void*, QPair >::iterator it = refCountingRanges->upperBound(start); if(it != refCountingRanges->begin()) { --it; if(((char*)it.key()) <= (char*)start && (char*)start < ((char*)it.key()) + it.value().first) { //Contained }else{ Q_ASSERT(0); } } Q_ASSERT(it.value().second > 0); --it.value().second; if(it.value().second == 0) refCountingRanges->erase(it); refCountingHasAdditionalRanges = !refCountingRanges->isEmpty(); }else{ Q_ASSERT(0); } if(!refCountingFirstRangeStart && !refCountingHasAdditionalRanges) doReferenceCounting = false; } void KDevelop::enableDUChainReferenceCounting(void* start, unsigned int size) { - SpinLock<> lock(refCountingLock); + QMutexLocker lock(&refCountingLock); doReferenceCounting = true; if(refCountingFirstRangeStart && ((char*)refCountingFirstRangeStart) <= (char*)start && (char*)start < ((char*)refCountingFirstRangeStart) + refCountingFirstRangeExtent.first) { //Increase the count for the first range ++refCountingFirstRangeExtent.second; }else if(refCountingHasAdditionalRanges || refCountingFirstRangeStart) { //There is additional ranges in the ranges-structure. Add any new ranges there as well. QMap< void*, QPair >::iterator it = refCountingRanges->upperBound(start); if(it != refCountingRanges->begin()) { --it; if(((char*)it.key()) <= (char*)start && (char*)start < ((char*)it.key()) + it.value().first) { //Contained, count up }else{ it = refCountingRanges->end(); //Insert own item } }else if(it != refCountingRanges->end() && it.key() > start) { //The item is behind it = refCountingRanges->end(); } if(it == refCountingRanges->end()) { QMap< void*, QPair >::iterator inserted = refCountingRanges->insert(start, qMakePair(size, 1u)); //Merge following ranges QMap< void*, QPair >::iterator it = inserted; ++it; while(it != refCountingRanges->end() && it.key() < ((char*)start) + size) { inserted.value().second += it.value().second; //Accumulate count if(((char*)start) + size < ((char*)inserted.key()) + it.value().first) { //Update end position inserted.value().first = (((char*)inserted.key()) + it.value().first) - ((char*)start); } it = refCountingRanges->erase(it); } }else{ ++it.value().second; if(it.value().first < size) it.value().first = size; } refCountingHasAdditionalRanges = true; }else{ refCountingFirstRangeStart = start; refCountingFirstRangeExtent.first = size; refCountingFirstRangeExtent.second = 1; } Q_ASSERT(refCountingHasAdditionalRanges == (refCountingRanges && !refCountingRanges->isEmpty())); #ifdef TEST_REFERENCE_COUNTING Q_ASSERT(shouldDoDUChainReferenceCounting(start)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)start + (size-1)))); #endif } #ifdef TEST_REFERENCE_COUNTING QAtomicInt& id() { static QAtomicInt& ret(KDevelop::globalItemRepositoryRegistry().getCustomCounter("referencer ids", 1)); return ret; } namespace KDevelop { ReferenceCountManager::ReferenceCountManager() : m_id(id().fetchAndAddRelaxed(1)) { } struct ReferenceCountItem { ///Item entries: ReferenceCountItem(uint id, uint target) : m_id(id), m_targetId(target) { } //Every item has to implement this function, and return a valid hash. //Must be exactly the same hash value as ReferenceCountItemRequest::hash() has returned while creating the item. unsigned int hash() const { return KDevHash() << m_id << m_targetId; } //Every item has to implement this function, and return the complete size this item takes in memory. //Must be exactly the same value as ReferenceCountItemRequest::itemSize() has returned while creating the item. unsigned short int itemSize() const { return sizeof(ReferenceCountItem); } uint m_id; uint m_targetId; ///Request entries: enum { AverageSize = 8 }; void createItem(ReferenceCountItem* item) const { *item = *this; } static void destroy(ReferenceCountItem* /*item*/, AbstractItemRepository&) { } static bool persistent(const ReferenceCountItem*) { return true; } bool equals(const ReferenceCountItem* item) const { return m_id == item->m_id && m_targetId == item->m_targetId; } }; static RepositoryManager< ItemRepository, false>& references() { static RepositoryManager< ItemRepository, false> referencesObject("Reference Count Debugging"); return referencesObject; } static RepositoryManager< ItemRepository, false>& oldReferences() { static RepositoryManager< ItemRepository, false> oldReferencesObject("Old Reference Count Debugging"); return oldReferencesObject; } void KDevelop::initReferenceCounting() { references(); oldReferences(); } ReferenceCountManager::~ReferenceCountManager() { //Make sure everything is cleaned up when the object is destroyed // Q_ASSERT(!references().contains(m_id)); } ReferenceCountManager::ReferenceCountManager(const ReferenceCountManager& rhs) : m_id(id().fetchAndAddRelaxed(1)) { //New id } ReferenceCountManager& ReferenceCountManager::ReferenceCountManager::operator=(const ReferenceCountManager& rhs) { //Keep id return *this; } // bool ReferenceCountManager::hasReferenceCount() const { // return references->findIndex(ReferenceCountItem); // } void ReferenceCountManager::increase(uint& ref, uint targetId) { Q_ASSERT(shouldDoDUChainReferenceCounting(this)); Q_ASSERT(!references->findIndex(ReferenceCountItem(m_id, targetId))); ++ref; { int oldIndex = oldReferences->findIndex(ReferenceCountItem(m_id, targetId)); if(oldIndex) oldReferences->deleteItem(oldIndex); } Q_ASSERT(references->index(ReferenceCountItem(m_id, targetId))); } void ReferenceCountManager::decrease(uint& ref, uint targetId) { Q_ASSERT(ref > 0); Q_ASSERT(shouldDoDUChainReferenceCounting(this)); Q_ASSERT(!oldReferences->findIndex(ReferenceCountItem(m_id, targetId))); uint refIndex = references->findIndex(ReferenceCountItem(m_id, targetId)); Q_ASSERT(refIndex); --ref; references->deleteItem(refIndex); oldReferences->index(ReferenceCountItem(m_id, targetId)); } } #else namespace KDevelop { void initReferenceCounting() { } } #endif diff --git a/serialization/referencecounting.h b/serialization/referencecounting.h index 3aa0244377..7e1a75d660 100644 --- a/serialization/referencecounting.h +++ b/serialization/referencecounting.h @@ -1,124 +1,124 @@ /* * This file is part of KDevelop * * Copyright 2009 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_REFERENCECOUNTING_H #define KDEVPLATFORM_REFERENCECOUNTING_H #include "serializationexport.h" #include #include +#include //When this is enabled, the duchain unloading is disabled as well, and you should start //with a cleared ~/.kdevduchain // #define TEST_REFERENCE_COUNTING -#include namespace KDevelop { ///Since shouldDoDUChainReferenceCounting is called extremely often, we export some internals into the header here, ///so the reference-counting code can be inlined. KDEVPLATFORMSERIALIZATION_EXPORT extern bool doReferenceCounting; - KDEVPLATFORMSERIALIZATION_EXPORT extern SpinLockData refCountingLock; + KDEVPLATFORMSERIALIZATION_EXPORT extern QMutex refCountingLock; KDEVPLATFORMSERIALIZATION_EXPORT extern QMap >* refCountingRanges; KDEVPLATFORMSERIALIZATION_EXPORT extern bool refCountingHasAdditionalRanges; KDEVPLATFORMSERIALIZATION_EXPORT extern void* refCountingFirstRangeStart; KDEVPLATFORMSERIALIZATION_EXPORT extern QPair refCountingFirstRangeExtent; KDEVPLATFORMSERIALIZATION_EXPORT void initReferenceCounting(); ///@internal The spin-lock ,must already be locked inline bool shouldDoDUChainReferenceCountingInternal(void* item) { QMap< void*, QPair >::iterator it = refCountingRanges->upperBound(item); if(it != refCountingRanges->begin()) { --it; return ((char*)it.key()) <= (char*)item && (char*)item < ((char*)it.key()) + it.value().first; } return false; } ///This is used by indexed items to decide whether they should do reference-counting inline bool shouldDoDUChainReferenceCounting(void* item) { if(!doReferenceCounting) //Fast path, no place has been marked for reference counting, 99% of cases return false; - SpinLock<> lock(refCountingLock); + QMutexLocker lock(&refCountingLock); if(refCountingFirstRangeStart && (((char*)refCountingFirstRangeStart) <= (char*)item) && ((char*)item < ((char*)refCountingFirstRangeStart) + refCountingFirstRangeExtent.first)) return true; if(refCountingHasAdditionalRanges) return shouldDoDUChainReferenceCountingInternal(item); else return false; } ///Enable reference-counting for the given range ///You should only enable the reference-counting for the time it's really needed, ///and it always has to be enabled too when the items are deleted again, else ///it will lead to inconsistencies in the repository. ///@warning If you are not working on the duchain internal storage mechanism, you should ///not care about this stuff at all. ///@param start Position where to start the reference-counting ///@param size Size of the area in bytes KDEVPLATFORMSERIALIZATION_EXPORT void enableDUChainReferenceCounting(void* start, unsigned int size); ///Must be called as often as enableDUChainReferenceCounting, with the same ranges ///Must never be called for the same range twice, and not for overlapping ranges ///@param start Position where to start the reference-counting ///@param size Size of the area in bytes KDEVPLATFORMSERIALIZATION_EXPORT void disableDUChainReferenceCounting(void* start); ///Use this as local variable within the object that maintains the reference-count, ///and use struct ReferenceCountManager { #ifndef TEST_REFERENCE_COUNTING inline void increase(uint& ref, uint /*targetId*/) { ++ref; } inline void decrease(uint& ref, uint /*targetId*/) { Q_ASSERT(ref); --ref; } #else ReferenceCountManager(); ~ReferenceCountManager(); ReferenceCountManager(const ReferenceCountManager& rhs); ReferenceCountManager& operator=(const ReferenceCountManager& rhs); void increase(uint& ref, uint targetId); void decrease(uint& ref, uint targetId); // bool hasReferenceCount() const; uint m_id; #endif }; } #endif diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index a2287bd2ae..e39a685d5f 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,97 +1,96 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") ########### next target ############### set(KDevPlatformUtil_LIB_SRCS autoorientedsplitter.cpp foregroundlock.cpp formattinghelpers.cpp richtextpushbutton.cpp kdevstringhandler.cpp focusedtreeview.cpp processlinemaker.cpp commandexecutor.cpp environmentselectionwidget.cpp environmentselectionmodel.cpp environmentgrouplist.cpp activetooltip.cpp executecompositejob.cpp fileutils.cpp shellutils.cpp mimetype.cpp multilevellistview.cpp objectlist.cpp placeholderitemproxymodel.cpp projecttestjob.cpp path.cpp debug.cpp ) set (KDevPlatformUtil_LIB_UI runoptions.ui ) if(NOT WIN32) add_subdirectory(dbus_socket_transformer) endif() add_subdirectory(duchainify) add_subdirectory(tests) ki18n_wrap_ui(KDevPlatformUtil_LIB_SRCS ${KDevPlatformUtil_LIB_US}) add_library(KDevPlatformUtil ${KDevPlatformUtil_LIB_SRCS}) add_library(KDev::Util ALIAS KDevPlatformUtil) generate_export_header(KDevPlatformUtil EXPORT_FILE_NAME utilexport.h) target_link_libraries(KDevPlatformUtil LINK_PUBLIC KF5::ConfigWidgets KF5::ItemModels LINK_PRIVATE KF5::KCMUtils KDev::Interfaces ) if(WIN32) target_include_directories(KDevPlatformUtil PUBLIC ${KDEWIN_INCLUDES}) target_link_libraries(KDevPlatformUtil PUBLIC ${KDEWIN_LIBRARIES}) endif() target_include_directories(KDevPlatformUtil INTERFACE "$") set_target_properties(KDevPlatformUtil PROPERTIES VERSION ${KDEVPLATFORM_LIB_VERSION} SOVERSION ${KDEVPLATFORM_LIB_SOVERSION} EXPORT_NAME Util) install(TARGETS KDevPlatformUtil EXPORT KDevPlatformTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES kdevplatform_shell_environment.sh DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) install( FILES kdev_format_source DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) ########### install files ############### install( FILES autoorientedsplitter.h foregroundlock.h formattinghelpers.h richtextpushbutton.h kdevstringhandler.h ksharedobject.h focusedtreeview.h activetooltip.h processlinemaker.h commandexecutor.h environmentselectionwidget.h environmentgrouplist.h pushvalue.h kdevvarlengtharray.h embeddedfreetree.h executecompositejob.h convenientfreelist.h - spinlock.h fileutils.h mimetype.h multilevellistview.h objectlist.h placeholderitemproxymodel.h projecttestjob.h path.h ${CMAKE_CURRENT_BINARY_DIR}/utilexport.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/util COMPONENT Devel) diff --git a/util/spinlock.cpp b/util/spinlock.cpp deleted file mode 100644 index 9b4deeee16..0000000000 --- a/util/spinlock.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -*/ - -#include "spinlock.h" - diff --git a/util/spinlock.h b/util/spinlock.h deleted file mode 100644 index cbdb904e39..0000000000 --- a/util/spinlock.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - - Copyright 2009 David Nolden - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -*/ - -#ifndef KDEVPLATFORM_SPINLOCK_H -#define KDEVPLATFORM_SPINLOCK_H - -#include -#include -#include -#include -#include - -namespace KDevelop { - -inline uint timevalToMilliSeconds(timeval v) { - return v.tv_sec * 1000 + v.tv_usec / 1000; -} - -struct SpinLockData { - SpinLockData() : needWake(false) { - lock = 0; - } - - QAtomicInt lock; //The variable that manages the lock - - volatile bool needWake; - QMutex waitingMutex; - QWaitCondition waitingCondition; -}; - -///This spin-lock uses a QWaitCondition together with a QMutex as fallback in case the lock is already taken. -///That means that this spin-lock is only more efficient than a QMutex when there is a low amount of lock-contention. -///When there is a high amount of lock-contention, QMutex should be more efficient. -///@note This implementation does not guarantee an optimal behavior as QMutex does: There is no guarantee -/// that a waiting thread is really woken up in exactly the moment the lock becomes available. -/// In _most_ cases it should work, but in worst-case the thread sleeps @p mSleep milliseconds. -///@param mSleep maximum number of milliseconds to sleep when waiting for the lock -///@param Timeout the timeount value -template -class SpinLock -{ - public: - - SpinLock(SpinLockData& data) : m_data(data), m_locked(true) { - if(m_data.lock.testAndSetOrdered(0, 1)) { - //Success at the first try - }else if(mTimeout) { - //Spin with timeout - - //Start spinning - timeval startTime; - gettimeofday(&startTime, 0); - - while(!m_data.lock.testAndSetOrdered(0, 1)) - { - timeval currentTime; - gettimeofday(¤tTime, 0); - timeval waited; - timersub(¤tTime, &startTime, &waited); - - if(mTimeout && timevalToMilliSeconds(waited) > mTimeout) { - m_locked = false; - break; - } - - //We need to wait now - QMutexLocker lock(&m_data.waitingMutex); - m_data.needWake = true; - m_data.waitingCondition.wait(&m_data.waitingMutex, mSleep); - } - }else{ - //No timeout - while(!m_data.lock.testAndSetOrdered(0, 1)) - { - //We need to wait now - QMutexLocker lock(&m_data.waitingMutex); - m_data.needWake = true; - m_data.waitingCondition.wait(&m_data.waitingMutex, mSleep); - } - } - } - - ~SpinLock() { - if(m_locked) { - bool needWake = m_data.needWake; - - m_data.needWake = false; - m_data.lock = 0; - - if(needWake) - m_data.waitingCondition.wakeOne(); - } - } - - //Returns whether the lock succeeded - bool locked() const { - return m_locked; - } - - private: - SpinLockData& m_data; - bool m_locked; -}; - -} - -#endif // KDEVPLATFORM_SPINLOCK_H