Use a single flush thread for ComputerManager

Some QSettings backends don't like concurrent writes and using a
single thread improves write batching behavior.
This commit is contained in:
Cameron Gutman 2022-10-25 02:07:16 -05:00
parent 59cae610d2
commit 16a9ed682e
2 changed files with 67 additions and 21 deletions

View file

@ -148,7 +148,7 @@ ComputerManager::ComputerManager(QObject *parent)
m_PollingRef(0),
m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr),
m_HostsListDirty(false)
m_NeedsDelayedFlush(false)
{
QSettings settings;
@ -164,6 +164,10 @@ ComputerManager::ComputerManager(QObject *parent)
// Fetch latest compatibility data asynchronously
m_CompatFetcher.start();
// Start the delayed flush thread to handle saveHosts() calls
m_DelayedFlushThread = new DelayedFlushThread(this);
m_DelayedFlushThread->start();
// To quit in a timely manner, we must block additional requests
// after we receive the aboutToQuit() signal. This is neccessary
// because NvHTTP uses aboutToQuit() to abort requests in progres
@ -174,6 +178,18 @@ ComputerManager::ComputerManager(QObject *parent)
ComputerManager::~ComputerManager()
{
// Stop the delayed flush thread before acquiring the lock in write mode
// to avoid deadlocking with a flush that needs the lock in read mode.
{
// Wake the delayed flush thread
m_DelayedFlushThread->requestInterruption();
m_DelayedFlushCondition.wakeOne();
// Wait for it to terminate (and finish any pending flush)
m_DelayedFlushThread->wait();
delete m_DelayedFlushThread;
}
QWriteLocker lock(&m_Lock);
// Delete machines that haven't been resolved yet
@ -203,19 +219,27 @@ ComputerManager::~ComputerManager()
}
}
class DeferredHostSaveTask : public QRunnable
{
public:
DeferredHostSaveTask(ComputerManager* cm)
: m_ComputerManager(cm) {}
void DelayedFlushThread::run() {
while (!QThread::currentThread()->isInterruptionRequested()) {
// Wait for a delayed flush request or an interruption
{
QMutexLocker locker(&m_ComputerManager->m_DelayedFlushMutex);
void run()
{
// Only persist the host list if it was dirty.
//
// NB: We can have multiple save tasks queued up and so another one could
// have persisted the changes already.
if (m_ComputerManager->m_HostsListDirty.testAndSetAcquire(true, false)) {
while (!QThread::currentThread()->isInterruptionRequested() && !m_ComputerManager->m_NeedsDelayedFlush) {
m_ComputerManager->m_DelayedFlushCondition.wait(&m_ComputerManager->m_DelayedFlushMutex);
}
// Reset the delayed flush flag to ensure any racing saveHosts() call will set it again
m_ComputerManager->m_NeedsDelayedFlush = false;
}
if (QThread::currentThread()->isInterruptionRequested()) {
// Bail without flushing if we woke up for an interruption
break;
}
// Perform the flush
{
QReadLocker lock(&m_ComputerManager->m_Lock);
QSettings settings;
@ -229,17 +253,17 @@ public:
settings.endArray();
}
}
private:
ComputerManager* m_ComputerManager;
};
}
void ComputerManager::saveHosts()
{
Q_ASSERT(m_DelayedFlushThread != nullptr && m_DelayedFlushThread->isRunning());
// Punt to a worker thread because QSettings on macOS can take ages (> 500 ms)
// to persist our host list to disk (especially when a host has a bunch of apps).
m_HostsListDirty.storeRelease(true);
QThreadPool::globalInstance()->start(new DeferredHostSaveTask(this));
QMutexLocker locker(&m_DelayedFlushMutex);
m_NeedsDelayedFlush = true;
m_DelayedFlushCondition.wakeOne();
}
QHostAddress ComputerManager::getBestGlobalAddressV6(QVector<QHostAddress> &addresses)

View file

@ -15,6 +15,25 @@
#include <QSettings>
#include <QRunnable>
#include <QTimer>
#include <QMutex>
#include <QWaitCondition>
class DelayedFlushThread : public QThread
{
Q_OBJECT
public:
DelayedFlushThread(ComputerManager* cm)
: m_ComputerManager(cm)
{
setObjectName("CM Delayed Flush Thread");
}
void run();
private:
ComputerManager* m_ComputerManager;
};
class MdnsPendingComputer : public QObject
{
@ -167,7 +186,7 @@ class ComputerManager : public QObject
friend class DeferredHostDeletionTask;
friend class PendingAddTask;
friend class PendingPairingTask;
friend class DeferredHostSaveTask;
friend class DelayedFlushThread;
public:
explicit ComputerManager(QObject *parent = nullptr);
@ -229,5 +248,8 @@ private:
QMdnsEngine::Cache m_MdnsCache;
QVector<MdnsPendingComputer*> m_PendingResolution;
CompatFetcher m_CompatFetcher;
QAtomicInteger<bool> m_HostsListDirty;
DelayedFlushThread* m_DelayedFlushThread;
QMutex m_DelayedFlushMutex;
QWaitCondition m_DelayedFlushCondition;
bool m_NeedsDelayedFlush;
};