kde-extraapps/kdevplatform/language/duchain/duchain.cpp
2015-07-26 14:23:17 +03:00

1721 lines
60 KiB
C++

/* This is part of KDevelop
Copyright 2006-2008 Hamish Rodda <rodda@kde.org>
Copyright 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
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 <unistd.h>
#include <QtCore/QCoreApplication>
#include <QApplication>
#include <QtCore/QHash>
#include <QtCore/QMultiMap>
#include <QtCore/QTimer>
#include <QtCore/QReadWriteLock>
#include <QtCore/QAtomicInt>
#include <QtCore/QThread>
#include <QtCore/QWaitCondition>
#include <QtCore/QMutex>
#include <KGlobal>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/icore.h>
#include <interfaces/ilanguage.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/foregroundlock.h>
#include <interfaces/isession.h>
#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<TopDUContext*> 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<DUChainBaseData*>(reinterpret_cast<char*>(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<DUChainBasePointer>("KDevelop::DUChainBasePointer");
qRegisterMetaType<DUContextPointer>("KDevelop::DUContextPointer");
qRegisterMetaType<TopDUContextPointer>("KDevelop::TopDUContextPointer");
qRegisterMetaType<DeclarationPointer>("KDevelop::DeclarationPointer");
qRegisterMetaType<FunctionDeclarationPointer>("KDevelop::FunctionDeclarationPointer");
qRegisterMetaType<KDevelop::IndexedString>("KDevelop::IndexedString");
qRegisterMetaType<KDevelop::IndexedTopDUContext>("KDevelop::IndexedTopDUContext");
qRegisterMetaType<KDevelop::ReferencedTopDUContext>("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<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;
}
void clear() {
if(!m_cleanupDisabled)
doMoreCleanup();
DUChainWriteLocker writeLock(DUChain::lock());
QMutexLocker l(&m_chainsMutex);
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;
QMutex m_cleanupMutex;
CleanupThread* m_cleanup;
DUChain* instance;
DUChainLock lock;
QMultiMap<IndexedString, TopDUContext*> m_chainsByUrl;
//Must be locked before accessing m_referenceCounts
QMutex m_referenceCountsMutex;
QHash<TopDUContext*, uint> m_referenceCounts;
Definitions m_definitions;
Uses m_uses;
QSet<uint> m_loading;
bool m_cleanupDisabled;
//List of available top-context indices, protected by m_chainsMutex
QVector<uint> m_availableTopContextIndices;
///Used to keep alive the top-context that belong to documents loaded in the editor
QSet<ReferencedTopDUContext> 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<ParsingEnvironmentFilePointer> getEnvironmentInformation(IndexedString url) {
QList<ParsingEnvironmentFilePointer> ret;
uint listIndex = m_environmentListInfo.findIndex(url);
if(listIndex) {
KDevVarLengthArray<uint> 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<uint>& 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<IndexedString> urls;
{
QMutexLocker lock(&m_chainsMutex);
urls += m_fileEnvironmentInformations.keys();
}
foreach(const IndexedString &url, urls) {
QList<ParsingEnvironmentFilePointer> 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<EnvironmentInformationItem*>(m_environmentInfo.itemFromIndex(index));
DUChainBaseData* theData = reinterpret_cast<DUChainBaseData*>(reinterpret_cast<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() {
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<ILanguage*> lockedParseMutexes;
QList<QReadWriteLock*> 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<TopDUContext*> 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<IndexedString> 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<IndexedString, ParsingEnvironmentFilePointer>::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<uint, ParsingEnvironmentFilePointer>::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<ParsingEnvironmentFile*>(DUChainItemSystem::self().create(
const_cast<DUChainBaseData*>(reinterpret_cast<const DUChainBaseData*>(reinterpret_cast<const 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<uint> 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<uint>& topContexts, IndexedTopDUContext top) {
if(topContexts.contains(top.index()))
return;
KSharedPtr<ParsingEnvironmentFile> 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<ParsingEnvironmentFile> > importers = info->importers();
QSet< KSharedPtr<ParsingEnvironmentFile> > checkNext;
//Do breadth first search, so less imports/importers have to be loaded, and a lower depth is reached
for(QList< KSharedPtr<ParsingEnvironmentFile> >::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<ParsingEnvironmentFile> >::const_iterator it = checkNext.begin(); it != checkNext.end(); ++it) {
topContexts.remove((*it)->indexedTopContext().index()); //Enable full check again
addContextsForRemoval(topContexts, (*it)->indexedTopContext());
}
}
}
template<class Entry>
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<uint> newItems;
{
QMutexLocker lock(&m_chainsMutex);
QMultiMap<IndexedString, ParsingEnvironmentFilePointer>::iterator start = m_fileEnvironmentInformations.lowerBound(url);
QMultiMap<IndexedString, ParsingEnvironmentFilePointer>::iterator end = m_fileEnvironmentInformations.upperBound(url);
for(QMultiMap<IndexedString, ParsingEnvironmentFilePointer>::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<uint> 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<IndexedString, ParsingEnvironmentFilePointer> m_fileEnvironmentInformations;
QHash<uint, ParsingEnvironmentFilePointer> 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<EnvironmentInformationListItem, EnvironmentInformationListRequest> m_environmentListInfo;
///Maps top-context-indices to environment-information item.
ItemRepository<EnvironmentInformationItem, EnvironmentInformationRequest> m_environmentInfo;
};
K_GLOBAL_STATIC(DUChainPrivate, sdDUChainPrivate)
DUChain::DUChain()
{
Q_ASSERT(ICore::self());
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
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<TopDUContext*> 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);
// 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);
// 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 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<uint> 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<IdentifiedFile, TopDUContext*>::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<ParsingEnvironmentFilePointer> 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<TopDUContext*> DUChain::chainsForDocument(const KUrl& document) const
{
return chainsForDocument(IndexedString(document));
}
QList<TopDUContext*> DUChain::chainsForDocument(const IndexedString& document) const
{
QList<TopDUContext*> chains;
if(sdDUChainPrivate->m_destroyed)
return chains;
QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
// Match all parsed versions of this document
for (QMultiMap<IndexedString, TopDUContext*>::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<ParsingEnvironmentFilePointer> 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<KUrl> DUChain::documents() const
{
QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
QList<KUrl> ret;
foreach(TopDUContext* top, sdDUChainPrivate->m_chainsByUrl.values()) {
ret << top->url().toUrl();
}
return ret;
}
QList<IndexedString> DUChain::indexedDocuments() const
{
QMutexLocker l(&sdDUChainPrivate->m_chainsMutex);
QList<IndexedString> 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<TopDUContext*> chains = chainsForDocument(url);
QList<KDevelop::ILanguage*> 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::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
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))
{
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(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;
waiter.m_dataMutex.lockInline();
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;
}
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 "moc_duchain.cpp"
// kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on