diff --git a/language/duchain/duchain.cpp b/language/duchain/duchain.cpp index 91da7f7921..019852dae0 100644 --- a/language/duchain/duchain.cpp +++ b/language/duchain/duchain.cpp @@ -1,1752 +1,1750 @@ /* 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 #include #include "../interfaces/ilanguagesupport.h" #include "../interfaces/icodehighlighting.h" #include "../backgroundparser/backgroundparser.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 "repositories/itemrepository.h" #include "waitforupdate.h" #include "referencecounting.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; 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); DUChainItemSystem::self().copy(*m_file->d_func(), *(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem)), true); Q_ASSERT((*(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))).m_range == m_file->d_func()->m_range); Q_ASSERT((*(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))).classId == m_file->d_func()->classId); Q_ASSERT((*(DUChainBaseData*)(((char*)item) + sizeof(EnvironmentInformationItem))).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() { m_waitMutex.lock(); m_stopRunning = true; m_wait.wakeAll(); //Wakes the thread up, so it notices it should exit m_waitMutex.unlock(); wait(); } private: void run() { while(1) { for(uint s = 0; s < cleanupEverySeconds; ++s) { if(m_stopRunning) break; m_waitMutex.lock(); m_wait.wait(&m_waitMutex, 1000); m_waitMutex.unlock(); } 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), 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"); notifier = new DUChainObserver(); 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); ParsingEnvironmentFile::m_staticData = (StaticParsingEnvironmentData*) new char[sizeof(StaticParsingEnvironmentData)]; if(opened) { kDebug() << "reading parsing-environment static data"; //Read f.read((char*)ParsingEnvironmentFile::m_staticData, sizeof(StaticParsingEnvironmentData)); }else{ kDebug() << "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() { kDebug() << "Destroying"; DUChain::m_deleted = true; m_cleanup->stopThread(); delete m_cleanup; delete instance; delete notifier; } 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 kDebug() << "removed a top-context that was reference-counted:" << context->url().str() << context->ownIndex(); m_referenceCounts.remove(context); } } uint index = context->ownIndex(); // kDebug(9505) << "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); 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; CleanupThread* m_cleanup; DUChain* instance; DUChainLock lock; QMultiMap m_chainsByUrl; //Must be locked before accessing m_referenceCounts QMutex m_referenceCountsMutex; QHash m_referenceCounts; DUChainObserver* notifier; 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 m_chainsMutex.lock(); bool removed = m_fileEnvironmentInformations.remove(info->url(), info); bool removed2 = m_indexEnvironmentInformations.remove(info->indexedTopContext().index()); m_chainsMutex.unlock(); { //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) { KSharedPtr< ParsingEnvironmentFile > p = ParsingEnvironmentFilePointer(loadInformation(topContextIndex)); if(p) { ret << p; }else{ kDebug() << "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); 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); 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(); kDebug() << "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(); kDebug() << "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 = (DUChainBaseData*)(((char*)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); kDebug(9505) << "Did not find stored item for" << url.str() << "count:" << m_fileEnvironmentInformations.values(url); } if(!atomic) { locker.unlock(); locker.lock(); } } } QMutex& cleanupMutex() { static QMutex mutex(QMutex::Recursive); return mutex; } ///@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(); kDebug(9505) << "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. kDebug(9505) << "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 == 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()); kDebug(9505) << "seconds spent doing cleanup: " << elapsedSeconds << "top-contexts still open:" << m_chainsByUrl.size(); } if(!retries) { int elapesedMilliSeconds = startTime.msecsTo(QTime::currentTime()); kDebug(9505) << "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; ParsingEnvironmentFile* ret = dynamic_cast(DUChainItemSystem::self().create( (DUChainBaseData*)(((char*)&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() ); kDebug() << "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()) { kDebug() << "removing top-context for" << top.data()->url().str() << "because it is out of date"; instance->removeDocumentChain(top.data()); } } kDebug() << "check ready"; } private: void addContextsForRemoval(QSet& topContexts, IndexedTopDUContext top) { if(topContexts.contains(top.index())) return; KSharedPtr 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< KSharedPtr > importers = info->importers(); QSet< KSharedPtr > checkNext; //Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached for(QList< KSharedPtr >::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< KSharedPtr >::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; }; K_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate) DUChain::DUChain() { - connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); - if(ICore::self()) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), SIGNAL(documentLoadedPrepare(KDevelop::IDocument*)), this, SLOT(documentLoadedPrepare(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(documentRenamed(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentActivated(KDevelop::IDocument*)), this, SLOT(documentActivated(KDevelop::IDocument*))); connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*))); } } 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 globalItemRepositoryRegistry(); 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(); globalAliasIdentifier(); } 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 ); branchModified(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 branchRemoved(context); 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); // kDebug(9505) << "duchain: adding document" << chain->url().str() << " " << chain; Q_ASSERT(chain); Q_ASSERT(!sdDUChainPrivate->hasChainForIndex(chain->ownIndex())); { SpinLock<> 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); //contextChanged(0L, DUChainObserver::Addition, DUChainObserver::ChildContexts, chain); if(ICore::self() && 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); } branchAdded(chain); } 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 KUrl& document, bool proxyContext) const { return chainForDocument(IndexedString(document.pathOrUrl()), 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); 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; QMutexLocker l(&sdDUChainPrivate->m_chainsMutex); /* { int count = 0; QMap::Iterator it = sdDUChainPrivate->m_chains.lowerBound(document); for( ; it != sdDUChainPrivate->m_chains.end() && it.key().url() == document.url(); ++it ) ++count; if( count > 1 ) kDebug(9505) << "found " << count << " chains for " << document.url().str(); }*/ //Eventually load an existing chain from disk l.unlock(); 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 KUrl& 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 (QMultiMap::Iterator 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 KUrl& 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); // kDebug() << document.str() << ": matching" << list.size() << (onlyProxyContexts ? "proxy-contexts" : (noProxyContexts ? "content-contexts" : "contexts")); QList< ParsingEnvironmentFilePointer>::const_iterator 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; } } DUChainObserver* DUChain::notifier() { return sdDUChainPrivate->notifier; } void DUChain::branchAdded(DUContext* context) { emit sdDUChainPrivate->notifier->branchAdded(DUContextPointer(context)); } void DUChain::branchModified(DUContext* context) { emit sdDUChainPrivate->notifier->branchModified(DUContextPointer(context)); } void DUChain::branchRemoved(DUContext* context) { emit sdDUChainPrivate->notifier->branchRemoved(DUContextPointer(context)); } 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; } /*Q_SCRIPTABLE bool DUChain::updateContext(TopDUContext* topContext, TopDUContext::Features minFeatures, QObject* notifyReady) const { if( (topContext->features() & minFeatures) != minFeatures || (topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->needsUpdate()) ) { ICore::self()->languageController()->backgroundParser()->addUpdateJob(topContext, minFeatures, notifyReady); return true; }else{ //No update needed, or we don't know since there's no ParsingEnvironmentFile attached return false; } }*/ 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); kDebug() << "highlighted" << doc->url() << "in foreground"; return; } }else{ kDebug() << "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? kWarning() << "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; } -void DUChain::aboutToQuit() +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()); 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 { //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(); } sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_openDocumentContexts.clear(); sdDUChainPrivate->m_destroyed = true; sdDUChainPrivate->clear(); 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)) { kWarning() << "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)) { //kWarning() << "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(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; waiter.m_dataMutex.lock(); { DUChainReadLocker readLock(DUChain::lock()); updateContextForUrl(document, minFeatures, &waiter); } // waiter.m_waitMutex.lock(); // waiter.m_dataMutex.unlock(); while(!waiter.m_ready) { 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() { sdDUChainPrivate->m_cleanupDisabled = true; } void DUChain::storeToDisk() { bool wasDisabled = sdDUChainPrivate->m_cleanupDisabled; sdDUChainPrivate->m_cleanupDisabled = false; sdDUChainPrivate->doMoreCleanup(); sdDUChainPrivate->m_cleanupDisabled = wasDisabled; } void DUChain::finalCleanup() { DUChainWriteLocker writeLock(DUChain::lock()); kDebug() << "doing final cleanup"; int cleaned = 0; while((cleaned = globalItemRepositoryRegistry().finalCleanup())) { kDebug() << "cleaned" << cleaned << "B"; if(cleaned < 1000) { kDebug() << "cleaned enough"; break; } } kDebug() << "final cleanup ready"; } bool DUChain::compareToDisk() { DUChainWriteLocker writeLock(DUChain::lock()); ///Step 1: Compare the repositories return true; } } #include "duchain.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/duchain/duchain.h b/language/duchain/duchain.h index d3cc6412af..a742d967cb 100644 --- a/language/duchain/duchain.h +++ b/language/duchain/duchain.h @@ -1,322 +1,327 @@ /* 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 DUCHAIN_H #define DUCHAIN_H #include #include "duchainobserver.h" #include "topducontext.h" class KUrl; namespace KDevelop { class IDocument; class TopDUContext; class DUChainLock; class IdentifiedFile; class ParsingEnvironmentManager; class ParsingEnvironment; class ParsingEnvironmentFile; typedef KSharedPtr 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 KUrl& 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 KUrl& 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 KUrl 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 KUrl& 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 KUrl 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 KUrl 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); 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 KUrl 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 the object which emits signals regarding duchain changes Q_SCRIPTABLE static DUChainObserver* notifier(); // Distribute the notifications /*static void contextChanged(DUContext* context, DUChainObserver::Modification change, DUChainObserver::Relationship relationship, DUChainBase* relatedObject = 0); static void declarationChanged(Declaration* declaration, DUChainObserver::Modification change, DUChainObserver::Relationship relationship, DUChainBase* relatedObject = 0); static void definitionChanged(Definition* definition, DUChainObserver::Modification change, DUChainObserver::Relationship relationship, DUChainBase* relatedObject = 0); static void useChanged(Use* use, DUChainObserver::Modification change, DUChainObserver::Relationship relationship, DUChainBase* relatedObject = 0);*/ /// Notify that a branch was added to \a context Q_SCRIPTABLE static void branchAdded(DUContext* context); /// Notify that a branch was modified within \a context Q_SCRIPTABLE static void branchModified(DUContext* context); /// Notify that a branch was removed from \a context Q_SCRIPTABLE static void branchRemoved(DUContext* context); /// 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(); ///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(); ///Does a big cleanup of the repositories, removing all items that are not needed any more for disk-persistency ///The duchain must not be locked when calling this ///@warning Only do this right after startup, or after shutdown, in a moment where indexed /// repository items are not needed any more, since the reference-counting mechanisms only respect disk persistency. /// All items that are not disk-referenced are invalidated during this step, which will lead to inconsistency, crashes, /// etc. when done in the wrong moment. void finalCleanup(); Q_SIGNALS: ///Is emitted when the declaration has been selected somewhere in the user-interface, for example in the completion-list void declarationSelected(DeclarationPointer decl); public Q_SLOTS: ///Removes the given top-context from the duchain, and deletes it. void removeDocumentChain(TopDUContext* document); ///Emits the declarationSelected signal, so other parties can notice it. void emitDeclarationSelected(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*); - void aboutToQuit(); + 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; /// 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 // DUCHAIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/shell/core.cpp b/shell/core.cpp index 8a121b59be..7bc50362ad 100644 --- a/shell/core.cpp +++ b/shell/core.cpp @@ -1,573 +1,575 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * 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 Library 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 "core.h" #include "core_p.h" #include #include #include #include #include #include #include #include #include #include #include #include "shellextension.h" #include "mainwindow.h" #include "sessioncontroller.h" #include "uicontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "partcontroller.h" #include "languagecontroller.h" #include "documentcontroller.h" #include "runcontroller.h" #include "session.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "progressmanager.h" #include "selectioncontroller.h" #include "debugcontroller.h" #include "kdevplatformversion.h" #include "workingsetcontroller.h" #include "testcontroller.h" #include #include #include #include volatile std::sig_atomic_t handlingSignal = 0; void shutdownGracefully(int sig) { if ( !handlingSignal ) { handlingSignal = 1; qDebug() << "signal " << sig << " received, shutting down gracefully"; QCoreApplication* app = QCoreApplication::instance(); if (QApplication* guiApp = qobject_cast(app)) { guiApp->closeAllWindows(); } app->quit(); return; } // re-raise signal with default handler and trigger program termination std::signal(sig, SIG_DFL); std::raise(sig); } void installSignalHandler() { #ifdef SIGHUP std::signal(SIGHUP, shutdownGracefully); #endif #ifdef SIGINT std::signal(SIGINT, shutdownGracefully); #endif #ifdef SIGTERM std::signal(SIGTERM, shutdownGracefully); #endif } namespace KDevelop { Core *Core::m_self = 0; KAboutData aboutData() { KAboutData aboutData( "kdevplatform", "kdevplatform", ki18n("KDevelop Platform"), KDEVPLATFORM_VERSION_STR, ki18n("Development Platform for IDE-like Applications"), KAboutData::License_LGPL_V2, ki18n( "Copyright 2004-2012, The KDevelop developers" ), KLocalizedString(), "http://www.kdevelop.org" ); aboutData.addAuthor( ki18n("Andreas Pakulat"), ki18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), "apaku@gmx.de" ); aboutData.addAuthor( ki18n("Alexander Dymo"), ki18n( "Architecture, Sublime UI, Ruby support" ), "adymo@kdevelop.org" ); aboutData.addAuthor( ki18n("David Nolden"), ki18n( "Definition-Use Chain, C++ Support" ), "david.nolden.kdevelop@art-master.de" ); aboutData.addAuthor( ki18n("Aleix Pol Gonzalez"), ki18n( "Co-Maintainer, CMake Support, Run Support, Kross Support" ), "aleixpol@kde.org" ); aboutData.addAuthor( ki18n("Vladimir Prus"), ki18n( "GDB integration" ), "ghost@cs.msu.su" ); aboutData.addAuthor( ki18n("Hamish Rodda"), ki18n( "Text editor integration, definition-use chain" ), "rodda@kde.org" ); aboutData.addCredit( ki18n("Matt Rogers"), KLocalizedString(), "mattr@kde.org"); aboutData.addCredit( ki18n("Cédric Pasteur"), ki18n("astyle and indent support"), "cedric.pasteur@free.fr" ); aboutData.addCredit( ki18n("Evgeniy Ivanov"), ki18n("Distributed VCS, Git, Mercurial"), "powerfox@kde.ru" ); //Veritas is outside in playground currently. //aboutData.addCredit( ki18n("Manuel Breugelmanns"), ki18n( "Veritas, QTest integraton"), "mbr.nxi@gmail.com" ); aboutData.addCredit( ki18n("Robert Gruber") , ki18n( "SnippetPart, debugger and usability patches" ), "rgruber@users.sourceforge.net" ); aboutData.addCredit( ki18n("Dukju Ahn"), ki18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), "dukjuahn@gmail.com" ); aboutData.addAuthor( ki18n("Niko Sams"), ki18n( "GDB integration, Webdevelopment Plugins" ), "niko.sams@gmail.com" ); aboutData.addAuthor( ki18n("Milian Wolff"), ki18n( "Co-Maintainer, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), "mail@milianw.de" ); return aboutData; } CorePrivate::CorePrivate(Core *core): m_componentData( aboutData() ), m_core(core), m_cleanedUp(false), m_shuttingDown(false) { } bool CorePrivate::initialize(Core::Setup mode, QString session ) { m_mode=mode; emit m_core->startupProgress(0); if( !sessionController ) { sessionController = new SessionController(m_core); } if( !workingSetController && !(mode & Core::NoUi) ) { workingSetController = new WorkingSetController(m_core); } kDebug() << "Creating ui controller"; if( !uiController ) { uiController = new UiController(m_core); } emit m_core->startupProgress(10); kDebug() << "Creating plugin controller"; if( !pluginController ) { pluginController = new PluginController(m_core); } if( !partController && !(mode & Core::NoUi)) { partController = new PartController(m_core, uiController.data()->defaultMainWindow()); } emit m_core->startupProgress(20); if( !projectController ) { projectController = new ProjectController(m_core); } if( !documentController ) { documentController = new DocumentController(m_core); } if( !languageController ) { // Must be initialized after documentController, because the background parser depends // on the document controller. languageController = new LanguageController(m_core); } emit m_core->startupProgress(25); if( !runController ) { runController = new RunController(m_core); } if( !sourceFormatterController ) { sourceFormatterController = new SourceFormatterController(m_core); } emit m_core->startupProgress(30); if ( !progressController) { progressController = new ProgressManager(); } if( !selectionController ) { selectionController = new SelectionController(m_core); } emit m_core->startupProgress(35); if( !documentationController && !(mode & Core::NoUi) ) { documentationController = new DocumentationController(m_core); } if( !debugController ) { debugController = new DebugController(m_core); } emit m_core->startupProgress(40); if( !testController ) { testController = new TestController(m_core); } emit m_core->startupProgress(47); kDebug() << "initializing ui controller"; sessionController.data()->initialize( session ); if( !sessionController.data()->activeSession() ) { return false; } emit m_core->startupProgress(55); // TODO: Is this early enough, or should we put the loading of the session into // the controller construct DUChain::initialize(); if(!(mode & Core::NoUi)) uiController.data()->initialize(); languageController.data()->initialize(); projectController.data()->initialize(); documentController.data()->initialize(); emit m_core->startupProgress(63); /* This is somewhat messy. We want to load the areas before loading the plugins, so that when each plugin is loaded we know if an area wants some of the tool view from that plugin. OTOH, loading of areas creates documents, and some documents might require that a plugin is already loaded. Probably, the best approach would be to plugins to just add tool views to a list of available tool view, and then grab those tool views when loading an area. */ kDebug() << "loading session plugins"; pluginController.data()->initialize(); emit m_core->startupProgress(78); if(!(mode & Core::NoUi)) { workingSetController.data()->initialize(); /* Need to do this after everything else is loaded. It's too hard to restore position of views, and toolbars, and whatever that are not created yet. */ uiController.data()->loadAllAreas(KGlobal::config()); uiController.data()->defaultMainWindow()->show(); } emit m_core->startupProgress(90); runController.data()->initialize(); sourceFormatterController.data()->initialize(); selectionController.data()->initialize(); if (documentationController) { documentationController.data()->initialize(); } emit m_core->startupProgress(95); debugController.data()->initialize(); testController.data()->initialize(); installSignalHandler(); if (partController) { // check features of kate and report to user if it does not fit KTextEditor::Document* doc = partController.data()->createTextPart(); if ( !qobject_cast< KTextEditor::MovingInterface* >(doc) ) { KMessageBox::error(QApplication::activeWindow(), i18n("The installed Kate version does not support the MovingInterface which is crucial for " "KDevelop starting from version 4.2.\n\n" "To use KDevelop with KDE SC prior to 4.6, where the SmartInterface is used instead " "of the MovingInterface, you need KDevelop 4.1 or lower.")); delete doc; return false; } delete doc; } emit m_core->startupProgress(100); return true; } CorePrivate::~CorePrivate() { delete selectionController.data(); delete projectController.data(); delete languageController.data(); delete pluginController.data(); delete uiController.data(); delete partController.data(); delete documentController.data(); delete runController.data(); delete sessionController.data(); delete sourceFormatterController.data(); delete documentationController.data(); delete debugController.data(); delete workingSetController.data(); delete testController.data(); selectionController.clear(); projectController.clear(); languageController.clear(); pluginController.clear(); uiController.clear(); partController.clear(); documentController.clear(); runController.clear(); sessionController.clear(); sourceFormatterController.clear(); documentationController.clear(); debugController.clear(); workingSetController.clear(); testController.clear(); } bool Core::initialize(KSplashScreen* splash, Setup mode, const QString& session ) { if (m_self) return true; m_self = new Core(); connect(m_self, SIGNAL(startupProgress(int)), splash, SLOT(progress(int))); bool ret = m_self->d->initialize(mode, session); if( splash ) { QTimer::singleShot( 200, splash, SLOT(deleteLater()) ); } if(ret) emit m_self->initializationCompleted(); return ret; } Core *KDevelop::Core::self() { return m_self; } Core::Core(QObject *parent) : ICore(parent) { d = new CorePrivate(this); connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(shutdown())); } Core::Core(CorePrivate* dd, QObject* parent) : ICore(parent), d(dd) { connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(shutdown())); } Core::~Core() { kDebug() ; //Cleanup already called before mass destruction of GUI delete d; m_self = 0; } Core::Setup Core::setupFlags() const { return d->m_mode; } void Core::shutdown() { if (!d->m_shuttingDown) { cleanup(); deleteLater(); } } bool Core::shuttingDown() const { return d->m_shuttingDown; } void Core::cleanup() { d->m_shuttingDown = true; emit aboutToShutdown(); if (!d->m_cleanedUp) { d->debugController.data()->cleanup(); d->selectionController.data()->cleanup(); // Save the layout of the ui here, so run it first d->uiController.data()->cleanup(); if (d->workingSetController) d->workingSetController.data()->cleanup(); /* Must be called before projectController.data()->cleanup(). */ // Closes all documents (discards, as already saved if the user wished earlier) d->documentController.data()->cleanup(); d->runController.data()->cleanup(); d->projectController.data()->cleanup(); d->sourceFormatterController.data()->cleanup(); d->pluginController.data()->cleanup(); d->sessionController.data()->cleanup(); d->testController.data()->cleanup(); //Disable the functionality of the language controller d->languageController.data()->cleanup(); + + DUChain::self()->shutdown(); } d->m_cleanedUp = true; } KComponentData Core::componentData() const { return d->m_componentData; } IUiController *Core::uiController() { return d->uiController.data(); } ISession* Core::activeSession() { return sessionController()->activeSession(); } SessionController *Core::sessionController() { return d->sessionController.data(); } UiController *Core::uiControllerInternal() { return d->uiController.data(); } IPluginController *Core::pluginController() { return d->pluginController.data(); } PluginController *Core::pluginControllerInternal() { return d->pluginController.data(); } IProjectController *Core::projectController() { return d->projectController.data(); } ProjectController *Core::projectControllerInternal() { return d->projectController.data(); } IPartController *Core::partController() { return d->partController.data(); } PartController *Core::partControllerInternal() { return d->partController.data(); } ILanguageController *Core::languageController() { return d->languageController.data(); } LanguageController *Core::languageControllerInternal() { return d->languageController.data(); } IDocumentController *Core::documentController() { return d->documentController.data(); } DocumentController *Core::documentControllerInternal() { return d->documentController.data(); } IRunController *Core::runController() { return d->runController.data(); } RunController *Core::runControllerInternal() { return d->runController.data(); } ISourceFormatterController* Core::sourceFormatterController() { return d->sourceFormatterController.data(); } SourceFormatterController* Core::sourceFormatterControllerInternal() { return d->sourceFormatterController.data(); } ProgressManager *Core::progressController() { return d->progressController.data(); } ISelectionController* Core::selectionController() { return d->selectionController.data(); } IDocumentationController* Core::documentationController() { return d->documentationController.data(); } DocumentationController* Core::documentationControllerInternal() { return d->documentationController.data(); } IDebugController* Core::debugController() { return d->debugController.data(); } DebugController* Core::debugControllerInternal() { return d->debugController.data(); } ITestController* Core::testController() { return d->testController.data(); } TestController* Core::testControllerInternal() { return d->testController.data(); } WorkingSetController* Core::workingSetControllerInternal() { return d->workingSetController.data(); } QString Core::version() { return KDEVPLATFORM_VERSION_STR; } } #include "core.moc"