Ensure all polling threads are dead before destroying hosts, even ones that have been detached by stopPollingAsync()

This commit is contained in:
Cameron Gutman 2019-01-19 19:16:09 -08:00
parent e0bebeae25
commit 75f631599c
2 changed files with 104 additions and 66 deletions

View file

@ -174,38 +174,19 @@ ComputerManager::~ComputerManager()
delete m_MdnsBrowser; delete m_MdnsBrowser;
m_MdnsBrowser = nullptr; m_MdnsBrowser = nullptr;
{ // Interrupt polling
QMutableMapIterator<QString, QThread*> i(m_PollThreads); for (ComputerPollingEntry* entry : m_PollEntries) {
entry->interrupt();
// Interrupt all polling threads
while (i.hasNext()) {
i.next();
i.value()->requestInterruption();
}
// Wait for all threads to terminate before destroying
// the NvComputer objects.
i.toFront();
while (i.hasNext()) {
i.next();
QThread* thread = i.value();
thread->wait();
delete thread;
i.remove();
}
} }
{ // Delete all polling entries (and associated threads)
QMutableMapIterator<QString, NvComputer*> i(m_KnownHosts); for (ComputerPollingEntry* entry : m_PollEntries) {
delete entry;
}
// Destroy all NvComputer objects // Destroy all NvComputer objects now that polling is halted
while (i.hasNext()) { for (NvComputer* computer : m_KnownHosts) {
i.next(); delete computer;
delete i.value();
i.remove();
}
} }
} }
@ -265,16 +246,22 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
return; return;
} }
if (m_PollThreads.contains(computer->uuid)) { ComputerPollingEntry* pollingEntry;
Q_ASSERT(m_PollThreads.value(computer->uuid)->isRunning());
return; if (!m_PollEntries.contains(computer->uuid)) {
pollingEntry = m_PollEntries[computer->uuid] = new ComputerPollingEntry();
}
else {
pollingEntry = m_PollEntries[computer->uuid];
} }
PcMonitorThread* thread = new PcMonitorThread(computer); if (!pollingEntry->isActive()) {
connect(thread, SIGNAL(computerStateChanged(NvComputer*)), PcMonitorThread* thread = new PcMonitorThread(computer);
this, SLOT(handleComputerStateChanged(NvComputer*))); connect(thread, SIGNAL(computerStateChanged(NvComputer*)),
m_PollThreads[computer->uuid] = thread; this, SLOT(handleComputerStateChanged(NvComputer*)));
thread->start(); pollingEntry->setActiveThread(thread);
thread->start();
}
} }
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer, void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
@ -317,14 +304,14 @@ public:
void run() void run()
{ {
QThread* pollingThread; ComputerPollingEntry* pollingEntry;
// Only do the minimum amount of work while holding the writer lock. // Only do the minimum amount of work while holding the writer lock.
// We must release it before calling saveHosts(). // We must release it before calling saveHosts().
{ {
QWriteLocker lock(&m_ComputerManager->m_Lock); QWriteLocker lock(&m_ComputerManager->m_Lock);
pollingThread = m_ComputerManager->m_PollThreads.take(m_Computer->uuid); pollingEntry = m_ComputerManager->m_PollEntries.take(m_Computer->uuid);
m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid); m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid);
} }
@ -332,18 +319,8 @@ public:
// Persist the new host list // Persist the new host list
m_ComputerManager->saveHosts(); m_ComputerManager->saveHosts();
// Delete the polling thread // Delete the polling entry first. This will stop all polling threads too.
if (pollingThread != nullptr) { delete pollingEntry;
pollingThread->requestInterruption();
// We must wait here because we're going to delete computer
// and we can't do that out from underneath the poller.
pollingThread->wait();
// The thread is safe to delete now
Q_ASSERT(pollingThread->isFinished());
delete pollingThread;
}
// Finally, delete the computer itself. This must be done // Finally, delete the computer itself. This must be done
// last because the polling thread might be using it. // last because the polling thread might be using it.
@ -498,20 +475,8 @@ void ComputerManager::stopPollingAsync()
m_MdnsBrowser = nullptr; m_MdnsBrowser = nullptr;
// Interrupt all threads, but don't wait for them to terminate // Interrupt all threads, but don't wait for them to terminate
QMutableMapIterator<QString, QThread*> i(m_PollThreads); for (ComputerPollingEntry* entry : m_PollEntries) {
while (i.hasNext()) { entry->interrupt();
i.next();
QThread* thread = i.value();
// Let this thread delete itself when it's finished
connect(thread, SIGNAL(finished()),
thread, SLOT(deleteLater()));
// The threads will delete themselves when they terminate,
// but we remove them from the polling threads map here.
i.value()->requestInterruption();
i.remove();
} }
} }

View file

@ -51,6 +51,79 @@ private:
QMdnsEngine::Resolver m_Resolver; QMdnsEngine::Resolver m_Resolver;
}; };
class ComputerPollingEntry
{
public:
ComputerPollingEntry()
: m_ActiveThread(nullptr)
{
}
virtual ~ComputerPollingEntry()
{
interrupt();
// interrupt() should have taken care of this
Q_ASSERT(m_ActiveThread == nullptr);
for (QThread* thread : m_InactiveList) {
thread->wait();
delete thread;
}
}
bool isActive()
{
cleanInactiveList();
return m_ActiveThread != nullptr;
}
void setActiveThread(QThread* thread)
{
cleanInactiveList();
Q_ASSERT(!isActive());
m_ActiveThread = thread;
}
void interrupt()
{
cleanInactiveList();
if (m_ActiveThread != nullptr) {
// Interrupt the active thread
m_ActiveThread->requestInterruption();
// Place it on the inactive list awaiting death
m_InactiveList.append(m_ActiveThread);
m_ActiveThread = nullptr;
}
}
private:
void cleanInactiveList()
{
QMutableListIterator<QThread*> i(m_InactiveList);
// Reap any threads that have finished
while (i.hasNext()) {
i.next();
QThread* thread = i.value();
if (thread->isFinished()) {
delete thread;
i.remove();
}
}
}
QThread* m_ActiveThread;
QList<QThread*> m_InactiveList;
};
class ComputerManager : public QObject class ComputerManager : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -100,7 +173,7 @@ private:
int m_PollingRef; int m_PollingRef;
QReadWriteLock m_Lock; QReadWriteLock m_Lock;
QMap<QString, NvComputer*> m_KnownHosts; QMap<QString, NvComputer*> m_KnownHosts;
QMap<QString, QThread*> m_PollThreads; QMap<QString, ComputerPollingEntry*> m_PollEntries;
QMdnsEngine::Server m_MdnsServer; QMdnsEngine::Server m_MdnsServer;
QMdnsEngine::Browser* m_MdnsBrowser; QMdnsEngine::Browser* m_MdnsBrowser;
QMdnsEngine::Cache m_MdnsCache; QMdnsEngine::Cache m_MdnsCache;