Files
qt-creator/src/plugins/cppeditor/stringtable.cpp

152 lines
3.8 KiB
C++
Raw Normal View History

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "stringtable.h"
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <QDebug>
#include <QElapsedTimer>
#include <QMutex>
#include <QSet>
#include <QThreadPool>
#include <QTimer>
#ifdef WITH_TESTS
#include <extensionsystem/pluginmanager.h>
#endif
namespace CppEditor::Internal {
enum {
GCTimeOut = 10 * 1000 // 10 seconds
};
enum {
DebugStringTable = 0
};
class StringTablePrivate : public QObject
{
public:
StringTablePrivate();
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
~StringTablePrivate() override { cancelAndWait(); }
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
void cancelAndWait();
QString insert(const QString &string);
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
void startGC();
void GC(QFutureInterface<void> &futureInterface);
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
QFuture<void> m_future;
QMutex m_lock;
QSet<QString> m_strings;
QTimer m_gcCountDown;
};
static StringTablePrivate *m_instance = nullptr;
StringTablePrivate::StringTablePrivate()
{
m_strings.reserve(1000);
m_gcCountDown.setObjectName(QLatin1String("StringTable::m_gcCountDown"));
m_gcCountDown.setSingleShot(true);
m_gcCountDown.setInterval(GCTimeOut);
connect(&m_gcCountDown, &QTimer::timeout, this, &StringTablePrivate::startGC);
}
QString StringTable::insert(const QString &string)
{
return m_instance->insert(string);
}
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
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;
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
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);
}
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
void StringTablePrivate::startGC()
{
QMutexLocker locker(&m_lock);
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
cancelAndWait();
m_future = Utils::runAsync(&StringTablePrivate::GC, this);
}
void StringTable::scheduleGC()
{
QMetaObject::invokeMethod(&m_instance->m_gcCountDown, QOverload<>::of(&QTimer::start),
Qt::QueuedConnection);
}
StringTable::StringTable()
{
m_instance = new StringTablePrivate;
}
StringTable::~StringTable()
{
delete m_instance;
m_instance = nullptr;
}
static inline bool isQStringInUse(const QString &string)
{
auto data_ptr = const_cast<QString&>(string).data_ptr();
return data_ptr->isShared() || !data_ptr->isMutable() /* QStringLiteral ? */;
}
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
void StringTablePrivate::GC(QFutureInterface<void> &futureInterface)
{
#ifdef WITH_TESTS
if (ExtensionSystem::PluginManager::isScenarioRunning("TestStringTable")) {
if (ExtensionSystem::PluginManager::finishScenario())
QThread::sleep(5);
}
#endif
int initialSize = 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<QString>::iterator i = m_strings.begin(); i != m_strings.end();) {
StringTable: Ensure only one GC() thread is running at a time The possible issue with the current implementation is that in theory many possible GC() are being executed in parallel. In this case just one of them is really working and others are waiting on the locked mutex (first line of the GC() method). In such a scenario when a call to StringTablePrivate::insert() is being executed from one more thread, it may happen that the working GC() thread is stopped (since m_stopGCRequested.fetchAndStoreAcquire(true) was executed from insert()) and later the mutex lock may be granted to the other awaiting GC() thread instead to the thread which executes insert() method. In this unlikely scenario the GC() thread won't be canceled and the lock inside the insert() method may be locked for considerable amount of time, what is not desired. The goal of this patch is to resolve the possible issue above and to simplify the code by eliminating the m_stopGCRequested variable and make use of QFuture.cancel() / QFuture.isCanceled() API instead. In addition, since we control now only one possible thread that executes the GC(), there is no need for future synchonizer anymore. GC() function can't be run in parallel in different threads, as the whole body of GC() is protected with mutex. This means that whenever a new scheduled call to GC() is being executed, this new call waits on the mentioned mutex at the beginning of GC(). So, instead of protecting the whole body of GC() with a mutex, we ensure that the old call to GC() is already finished (if not, we also cancel the old call) while preparing an asynchronous call to start a new GC() from inside startGC() method. Whenever we are calling the insert() method, we still protect the access to m_strings with a mutex (as insert() is designed to be called from different threads in parallel). Just after locking the mutex we are canceling any possible ongoing call to GC(). After canceling the GC() call, we are sure that no new call to GC() will be executed until we unlock the mutex, so it's safe now to modify the m_string data. Change-Id: If72d0a6f98fb414c6c63117bc9baa667d17e1ffe Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2021-03-09 18:24:04 +01:00
if (futureInterface.isCanceled())
return;
if (!isQStringInUse(*i))
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;
}
}
} // CppEditor::Internal