File indexing completed on 2024-05-12 16:10:37
0001 /* 0002 SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "itemrepositoryregistry.h" 0008 0009 #include <QDir> 0010 #include <QProcessEnvironment> 0011 #include <QCoreApplication> 0012 #include <QDataStream> 0013 #include <QMutexLocker> 0014 #include <QRecursiveMutex> 0015 #include <QStandardPaths> 0016 0017 #include <KLocalizedString> 0018 0019 #include <util/shellutils.h> 0020 0021 #include "abstractitemrepository.h" 0022 #include "debug.h" 0023 0024 #include <mutex> 0025 #include <set> 0026 #include <utility> 0027 0028 using namespace KDevelop; 0029 0030 namespace { 0031 //If KDevelop crashed this many times consecutively, clean up the repository 0032 const int crashesBeforeCleanup = 1; 0033 0034 void setCrashCounter(QFile& crashesFile, int count) 0035 { 0036 crashesFile.close(); 0037 crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate); 0038 QDataStream writeStream(&crashesFile); 0039 writeStream << count; 0040 } 0041 0042 bool shouldClear(const QString& path) 0043 { 0044 QDir dir(path); 0045 0046 if (!dir.exists()) { 0047 return false; 0048 } 0049 0050 if (getenv("CLEAR_DUCHAIN_DIR")) { 0051 qCDebug(SERIALIZATION) << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set"; 0052 return true; 0053 } 0054 0055 if (dir.exists(QStringLiteral("is_writing"))) { 0056 qCWarning(SERIALIZATION) << "repository" << path << "was write-locked, it probably is inconsistent"; 0057 return true; 0058 } 0059 0060 if (!dir.exists(QStringLiteral("version_%1").arg(staticItemRepositoryVersion()))) { 0061 qCWarning(SERIALIZATION) << "version mismatch or no version hint; expected version:" << 0062 staticItemRepositoryVersion(); 0063 return true; 0064 } 0065 0066 QFile crashesFile(dir.filePath(QStringLiteral("crash_counter"))); 0067 if (crashesFile.open(QIODevice::ReadOnly)) { 0068 int count; 0069 QDataStream stream(&crashesFile); 0070 stream >> count; 0071 0072 qCDebug(SERIALIZATION) << "current count of crashes: " << count; 0073 0074 if (count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) { 0075 bool userAnswer = askUser(i18np("The previous session crashed.", "Session crashed %1 times in a row.", 0076 count), 0077 i18nc("tty action", "Clear cache"), 0078 i18nc("@title", "Session Crashed"), 0079 i18n("The crash may be caused by a corruption of cached data.\n\n" 0080 "Press Clear if you want KDevelop to clear the cache, otherwise press Continue if you are sure the crash has another origin."), 0081 i18nc("@action:button", "Clear Cache"), 0082 i18nc("@action:button", "Continue")); 0083 if (userAnswer) { 0084 qCDebug(SERIALIZATION) << "User chose to clean repository"; 0085 return true; 0086 } else { 0087 setCrashCounter(crashesFile, 1); 0088 qCDebug(SERIALIZATION) << "User chose to reset crash counter"; 0089 } 0090 } else { 0091 ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly. 0092 setCrashCounter(crashesFile, ++count); 0093 } 0094 } else { 0095 setCrashCounter(crashesFile, 1); 0096 } 0097 0098 return false; 0099 } 0100 } 0101 0102 namespace KDevelop { 0103 class ItemRepositoryRegistryPrivate 0104 { 0105 public: 0106 bool m_shallDelete = false; 0107 bool m_wasShutdown = false; 0108 QString m_path; 0109 // TODO: is the order of repositories important? If not, store them in a QSet rather than std::set. 0110 std::set<AbstractItemRepository*> m_repositories; 0111 QMap<QString, QAtomicInt*> m_customCounters; 0112 mutable QRecursiveMutex m_mutex; 0113 0114 /// @param path A shared directory-path that the item-repositories are to be loaded from. 0115 /// @note Currently the given path must reference a hidden directory, just to make sure we're 0116 /// not accidentally deleting something important. 0117 explicit ItemRepositoryRegistryPrivate(const QString& path); 0118 0119 QAtomicInt& customCounter(const QString& identity, int initialValue); 0120 void lockForWriting(); 0121 void unlockForWriting(); 0122 void deleteDataDirectory(bool recreate = true); 0123 }; 0124 0125 //The global item-repository registry 0126 ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = nullptr; 0127 0128 ItemRepositoryRegistry::ItemRepositoryRegistry(const QString& repositoryPath) 0129 : d_ptr(new ItemRepositoryRegistryPrivate(repositoryPath)) 0130 { 0131 } 0132 0133 void ItemRepositoryRegistry::initialize(const QString& repositoryPath) 0134 { 0135 if (!m_self) { 0136 ///We intentionally leak the registry, to prevent problems in the destruction order, where 0137 ///the actual repositories might get deleted later than the repository registry. 0138 m_self = new ItemRepositoryRegistry(repositoryPath); 0139 } 0140 m_self->d_func()->m_wasShutdown = false; 0141 m_self->d_func()->m_shallDelete = false; 0142 } 0143 0144 ItemRepositoryRegistry* ItemRepositoryRegistry::self() 0145 { 0146 Q_ASSERT(m_self); 0147 return m_self; 0148 } 0149 0150 void ItemRepositoryRegistry::deleteRepositoryFromDisk(const QString& repositoryPath) 0151 { 0152 // Now, as we have only the global item-repository registry, assume that if and only if 0153 // the given session is ours, its cache path is used by the said global item-repository registry. 0154 if (m_self && !m_self->d_func()->m_wasShutdown && m_self->d_func()->m_path == repositoryPath) { 0155 // remove later 0156 m_self->d_func()->m_shallDelete = true; 0157 } else { 0158 // Otherwise, given session is not ours. 0159 // remove its item-repository directory directly. 0160 QDir(repositoryPath).removeRecursively(); 0161 } 0162 } 0163 0164 QRecursiveMutex& ItemRepositoryRegistry::mutex() 0165 { 0166 Q_D(ItemRepositoryRegistry); 0167 0168 return d->m_mutex; 0169 } 0170 0171 QAtomicInt& ItemRepositoryRegistryPrivate::customCounter(const QString& identity, int initialValue) 0172 { 0173 auto customCounterIt = m_customCounters.find(identity); 0174 if (customCounterIt == m_customCounters.end()) { 0175 customCounterIt = m_customCounters.insert(identity, new QAtomicInt(initialValue)); 0176 } 0177 return **customCounterIt; 0178 } 0179 0180 QAtomicInt& ItemRepositoryRegistry::customCounter(const QString& identity, int initialValue) 0181 { 0182 Q_D(ItemRepositoryRegistry); 0183 0184 return d->customCounter(identity, initialValue); 0185 } 0186 0187 ///The global item-repository registry that is used by default 0188 ItemRepositoryRegistry& globalItemRepositoryRegistry() 0189 { 0190 return *ItemRepositoryRegistry::self(); 0191 } 0192 0193 void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository) 0194 { 0195 Q_D(ItemRepositoryRegistry); 0196 0197 QMutexLocker lock(&d->m_mutex); 0198 d->m_repositories.insert(repository); 0199 if (!d->m_path.isEmpty()) { 0200 // Locking the repository is documented as the caller's responsibility. 0201 if (!repository->open(d->m_path)) { 0202 d->deleteDataDirectory(); 0203 qCritical() << "failed to open a repository"; 0204 abort(); 0205 } 0206 } 0207 } 0208 0209 QString ItemRepositoryRegistry::path() const 0210 { 0211 Q_D(const ItemRepositoryRegistry); 0212 0213 //We cannot lock the mutex here, since this may be called with one of the repositories locked, 0214 //and that may lead to a deadlock when at the same time a storing is requested 0215 return d->m_path; 0216 } 0217 0218 void ItemRepositoryRegistryPrivate::lockForWriting() 0219 { 0220 QMutexLocker lock(&m_mutex); 0221 //Create is_writing 0222 QFile f(m_path + QLatin1String("/is_writing")); 0223 f.open(QIODevice::WriteOnly); 0224 f.close(); 0225 } 0226 0227 void ItemRepositoryRegistry::lockForWriting() 0228 { 0229 Q_D(ItemRepositoryRegistry); 0230 0231 d->lockForWriting(); 0232 } 0233 0234 void ItemRepositoryRegistryPrivate::unlockForWriting() 0235 { 0236 QMutexLocker lock(&m_mutex); 0237 //Delete is_writing 0238 QFile::remove(m_path + QLatin1String("/is_writing")); 0239 } 0240 0241 void ItemRepositoryRegistry::unlockForWriting() 0242 { 0243 Q_D(ItemRepositoryRegistry); 0244 0245 d->unlockForWriting(); 0246 } 0247 0248 void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository) 0249 { 0250 Q_D(ItemRepositoryRegistry); 0251 0252 QMutexLocker lock(&d->m_mutex); 0253 Q_ASSERT(d->m_repositories.count(repository) == 1); 0254 d->m_repositories.erase(repository); 0255 } 0256 0257 //After calling this, the data-directory may be a new one 0258 void ItemRepositoryRegistryPrivate::deleteDataDirectory(bool recreate) 0259 { 0260 QMutexLocker lock(&m_mutex); 0261 0262 bool result = QDir(m_path).removeRecursively(); 0263 Q_ASSERT(result); 0264 Q_UNUSED(result); 0265 // Just recreate the directory then; leave old path (as it is dependent on appname and session only). 0266 if (recreate) { 0267 QDir().mkpath(m_path); 0268 } 0269 } 0270 0271 ItemRepositoryRegistryPrivate::ItemRepositoryRegistryPrivate(const QString& path) 0272 : m_path(path) 0273 { 0274 Q_ASSERT(!path.isEmpty()); 0275 0276 // Check if the repository shall be cleared 0277 if (shouldClear(path)) { 0278 qCWarning(SERIALIZATION) << QStringLiteral("The data-repository at %1 has to be cleared.").arg(path); 0279 deleteDataDirectory(false); 0280 } 0281 0282 QDir().mkpath(path); 0283 0284 QFile f(path + QLatin1String("/Counters")); 0285 if (f.open(QIODevice::ReadOnly)) { 0286 QDataStream stream(&f); 0287 0288 while (!stream.atEnd()) { 0289 //Read in all custom counter values 0290 QString counterName; 0291 stream >> counterName; 0292 int counterValue; 0293 stream >> counterValue; 0294 customCounter(counterName, 0) = counterValue; 0295 } 0296 } 0297 } 0298 0299 void ItemRepositoryRegistry::store() 0300 { 0301 Q_D(ItemRepositoryRegistry); 0302 0303 QMutexLocker lock(&d->m_mutex); 0304 for (auto* repository : std::as_const(d->m_repositories)) { 0305 std::scoped_lock repoLock(*repository); 0306 repository->store(); 0307 } 0308 0309 QFile versionFile(d->m_path + QStringLiteral("/version_%1").arg(staticItemRepositoryVersion())); 0310 if (versionFile.open(QIODevice::WriteOnly)) { 0311 versionFile.close(); 0312 } else { 0313 qCWarning(SERIALIZATION) << "Could not open version file for writing"; 0314 } 0315 0316 //Store all custom counter values 0317 QFile f(d->m_path + QLatin1String("/Counters")); 0318 if (f.open(QIODevice::WriteOnly)) { 0319 f.resize(0); 0320 QDataStream stream(&f); 0321 for (QMap<QString, QAtomicInt*>::const_iterator it = d->m_customCounters.constBegin(); 0322 it != d->m_customCounters.constEnd(); 0323 ++it) { 0324 stream << it.key(); 0325 stream << it.value()->fetchAndAddRelaxed(0); 0326 } 0327 } else { 0328 qCWarning(SERIALIZATION) << "Could not open counter file for writing"; 0329 } 0330 } 0331 0332 void ItemRepositoryRegistry::printAllStatistics() const 0333 { 0334 Q_D(const ItemRepositoryRegistry); 0335 0336 QMutexLocker lock(&d->m_mutex); 0337 for (auto* repository : std::as_const(d->m_repositories)) { 0338 std::scoped_lock repoLock(*repository); 0339 qCDebug(SERIALIZATION) << "statistics in" << repository->repositoryName() << ":"; 0340 qCDebug(SERIALIZATION) << repository->printStatistics(); 0341 } 0342 } 0343 0344 int ItemRepositoryRegistry::finalCleanup() 0345 { 0346 Q_D(ItemRepositoryRegistry); 0347 0348 QMutexLocker lock(&d->m_mutex); 0349 int changed = false; 0350 for (auto* repository : std::as_const(d->m_repositories)) { 0351 std::scoped_lock repoLock(*repository); 0352 int added = repository->finalCleanup(); 0353 changed += added; 0354 qCDebug(SERIALIZATION) << "cleaned in" << repository->repositoryName() << ":" << added; 0355 } 0356 0357 return changed; 0358 } 0359 0360 ItemRepositoryRegistry::~ItemRepositoryRegistry() 0361 { 0362 Q_D(const ItemRepositoryRegistry); 0363 0364 for (auto* repository : std::as_const(d->m_repositories)) { 0365 std::scoped_lock repoLock(*repository); 0366 repository->close(); 0367 } 0368 0369 for (QAtomicInt* counter : qAsConst(d->m_customCounters)) { 0370 delete counter; 0371 } 0372 } 0373 0374 void ItemRepositoryRegistry::shutdown() 0375 { 0376 Q_D(ItemRepositoryRegistry); 0377 0378 QMutexLocker lock(&d->m_mutex); 0379 0380 // FIXME: we don't close since this can trigger crashes at shutdown 0381 // since some items are still referenced, e.g. in static variables 0382 // NOTE: ItemRepositoryRegistryPrivate::close() used to close all contained repositories and clear d->m_path 0383 // under d->m_mutex lock before its code was moved into ~ItemRepositoryRegistry(). 0384 // If this closing is ever uncommented, d->m_path could be cleared after its use in the code below. 0385 // d->close(); 0386 0387 if (d->m_shallDelete) { 0388 d->deleteDataDirectory(false); 0389 } else { 0390 QFile::remove(d->m_path + QLatin1String("/crash_counter")); 0391 } 0392 0393 d->m_wasShutdown = true; 0394 } 0395 }