// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "stringtable.h" #include "async.h" #include #include #include #include #include #include // FIXME: Provide tst_StringTable that would run GC, make a sleep inside GC being run in other // thread and execute destructor of StringTable in main thread. In this case the test should // ensure that destructor of StringTable waits for its internal thread to finish. using namespace std::chrono; namespace Utils::StringTable { enum { DebugStringTable = 0 }; static std::atomic_bool s_isScheduled = false; class StringTablePrivate { public: StringTablePrivate() { m_strings.reserve(1000); } ~StringTablePrivate() { cancelAndWait(); } void cancelAndWait(); QString insert(const QString &string); void startGC(); void GC(QPromise &promise); QFuture m_future; QMutex m_lock; QSet m_strings; }; static StringTablePrivate &stringTable() { static StringTablePrivate theStringTable; return theStringTable; } QTCREATOR_UTILS_EXPORT QString insert(const QString &string) { return stringTable().insert(string); } void StringTablePrivate::cancelAndWait() { if (!m_future.isRunning()) return; m_future.cancel(); m_future.waitForFinished(); } QString StringTablePrivate::insert(const QString &string) { if (string.isEmpty()) return string; QMutexLocker locker(&m_lock); // From this point of time any possible new call to startGC() will be held until // we finish this function. So we are sure that after canceling the running GC() method now, // no new call to GC() will be executed until we finish this function. cancelAndWait(); // A possibly running GC() thread already finished, so it's safe to modify m_strings from // now until we unlock the mutex. return *m_strings.insert(string); } void StringTablePrivate::startGC() { s_isScheduled.exchange(false); QMutexLocker locker(&m_lock); cancelAndWait(); m_future = Utils::asyncRun(&StringTablePrivate::GC, this); } QTCREATOR_UTILS_EXPORT void scheduleGC() { if (!s_isScheduled.exchange(true)) QTimer::singleShot(10s, qApp, [] { stringTable().startGC(); }); } static inline bool isDetached(const QString &string, int &bytesSaved) { if (DebugStringTable) { QStringPrivate &data_ptr = const_cast(string).data_ptr(); const int ref = data_ptr->d_ptr()->ref_; bytesSaved += (ref - 1) * string.size(); if (ref > 10) qDebug() << ref << string.size() << string.left(50); } return string.isDetached(); } void StringTablePrivate::GC(QPromise &promise) { int initialSize = 0; int bytesSaved = 0; QElapsedTimer timer; if (DebugStringTable) { initialSize = m_strings.size(); timer.start(); } // Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.) for (QSet::iterator i = m_strings.begin(); i != m_strings.end();) { if (promise.isCanceled()) return; if (isDetached(*i, bytesSaved)) i = m_strings.erase(i); else ++i; } if (DebugStringTable) { const int currentSize = m_strings.size(); qDebug() << "StringTable::GC removed" << initialSize - currentSize << "strings in" << timer.elapsed() << "ms, size is now" << currentSize << "saved: " << bytesSaved << "bytes"; } } } // Utils::StringTable