forked from qt-creator/qt-creator
Core: Fix Reaper blocking shutdown
The eventloop is blocked while the destructor of ReaperPrivate is running. So drive the ProcessReaper by hand instead. Change-Id: I691a28f27455f58ae5807540746ffa1aa2783fed Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
@@ -29,6 +29,8 @@
|
|||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
@@ -36,6 +38,8 @@ static ReaperPrivate *d = nullptr;
|
|||||||
|
|
||||||
ProcessReaper::ProcessReaper(QProcess *p, int timeoutMs) : m_process(p)
|
ProcessReaper::ProcessReaper(QProcess *p, int timeoutMs) : m_process(p)
|
||||||
{
|
{
|
||||||
|
d->m_reapers.append(this);
|
||||||
|
|
||||||
m_iterationTimer.setInterval(timeoutMs);
|
m_iterationTimer.setInterval(timeoutMs);
|
||||||
m_iterationTimer.setSingleShot(true);
|
m_iterationTimer.setSingleShot(true);
|
||||||
connect(&m_iterationTimer, &QTimer::timeout, this, &ProcessReaper::nextIteration);
|
connect(&m_iterationTimer, &QTimer::timeout, this, &ProcessReaper::nextIteration);
|
||||||
@@ -44,11 +48,31 @@ ProcessReaper::ProcessReaper(QProcess *p, int timeoutMs) : m_process(p)
|
|||||||
m_futureInterface.reportStarted();
|
m_futureInterface.reportStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessReaper::~ProcessReaper()
|
||||||
|
{
|
||||||
|
d->m_reapers.removeOne(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProcessReaper::timeoutMs() const
|
||||||
|
{
|
||||||
|
const int remaining = m_iterationTimer.remainingTime();
|
||||||
|
if (remaining < 0)
|
||||||
|
return m_iterationTimer.interval();
|
||||||
|
m_iterationTimer.stop();
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessReaper::isFinished() const
|
||||||
|
{
|
||||||
|
return !m_process;
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessReaper::nextIteration()
|
void ProcessReaper::nextIteration()
|
||||||
{
|
{
|
||||||
QProcess::ProcessState state = m_process->state();
|
QProcess::ProcessState state = m_process->state();
|
||||||
if (state == QProcess::NotRunning || m_emergencyCounter > 5) {
|
if (state == QProcess::NotRunning || m_emergencyCounter > 5) {
|
||||||
delete m_process;
|
delete m_process;
|
||||||
|
m_process = nullptr;
|
||||||
m_futureInterface.reportFinished();
|
m_futureInterface.reportFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -76,6 +100,30 @@ ReaperPrivate::ReaperPrivate()
|
|||||||
|
|
||||||
ReaperPrivate::~ReaperPrivate()
|
ReaperPrivate::~ReaperPrivate()
|
||||||
{
|
{
|
||||||
|
while (!m_reapers.isEmpty()) {
|
||||||
|
int alreadyWaited = 0;
|
||||||
|
QList<ProcessReaper *> toDelete;
|
||||||
|
|
||||||
|
// push reapers along:
|
||||||
|
foreach (ProcessReaper *pr, m_reapers) {
|
||||||
|
const int timeoutMs = pr->timeoutMs();
|
||||||
|
if (alreadyWaited < timeoutMs) {
|
||||||
|
const unsigned long toSleep = static_cast<unsigned long>(timeoutMs - alreadyWaited);
|
||||||
|
QThread::msleep(toSleep);
|
||||||
|
alreadyWaited += toSleep;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr->nextIteration();
|
||||||
|
|
||||||
|
if (pr->isFinished())
|
||||||
|
toDelete.append(pr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean out reapers that finished in the meantime
|
||||||
|
qDeleteAll(toDelete);
|
||||||
|
toDelete.clear();
|
||||||
|
}
|
||||||
|
|
||||||
d = nullptr;
|
d = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,23 +138,7 @@ void reap(QProcess *process, int timeoutMs)
|
|||||||
|
|
||||||
QTC_ASSERT(Internal::d, return);
|
QTC_ASSERT(Internal::d, return);
|
||||||
|
|
||||||
auto reaper = new Internal::ProcessReaper(process, timeoutMs);
|
new Internal::ProcessReaper(process, timeoutMs);
|
||||||
QFuture<void> f = reaper->future();
|
|
||||||
|
|
||||||
Internal::d->m_synchronizer.addFuture(f);
|
|
||||||
auto watcher = new QFutureWatcher<void>();
|
|
||||||
|
|
||||||
QObject::connect(watcher, &QFutureWatcher<void>::finished, [watcher, reaper]() {
|
|
||||||
watcher->deleteLater();
|
|
||||||
|
|
||||||
const QList<QFuture<void>> futures = Utils::filtered(Internal::d->m_synchronizer.futures(),
|
|
||||||
[reaper](const QFuture<void> &f) { return reaper->future() != f; });
|
|
||||||
for (const QFuture<void> &f : futures)
|
|
||||||
Internal::d->m_synchronizer.addFuture(f);
|
|
||||||
|
|
||||||
delete reaper;
|
|
||||||
});
|
|
||||||
watcher->setFuture(f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Reaper
|
} // namespace Reaper
|
||||||
|
|||||||
@@ -26,8 +26,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QFutureSynchronizer>
|
#include <QFutureInterface>
|
||||||
#include <QFutureWatcher>
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
@@ -42,13 +41,14 @@ class ProcessReaper : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ProcessReaper(QProcess *p, int timeoutMs);
|
ProcessReaper(QProcess *p, int timeoutMs);
|
||||||
|
~ProcessReaper();
|
||||||
|
|
||||||
QFuture<void> future() { return m_futureInterface.future(); }
|
int timeoutMs() const;
|
||||||
|
bool isFinished() const;
|
||||||
private:
|
|
||||||
void nextIteration();
|
void nextIteration();
|
||||||
|
|
||||||
QTimer m_iterationTimer;
|
private:
|
||||||
|
mutable QTimer m_iterationTimer;
|
||||||
QFutureInterface<void> m_futureInterface;
|
QFutureInterface<void> m_futureInterface;
|
||||||
QProcess *m_process;
|
QProcess *m_process;
|
||||||
int m_emergencyCounter = 0;
|
int m_emergencyCounter = 0;
|
||||||
@@ -59,7 +59,7 @@ class ReaperPrivate {
|
|||||||
public:
|
public:
|
||||||
~ReaperPrivate();
|
~ReaperPrivate();
|
||||||
|
|
||||||
QFutureSynchronizer<void> m_synchronizer;
|
QList<ProcessReaper *> m_reapers;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ReaperPrivate();
|
ReaperPrivate();
|
||||||
|
|||||||
Reference in New Issue
Block a user