Files
qt-creator/src/plugins/coreplugin/reaper.cpp
Jarek Kobus c525bb15d7 Fix a possible crash in ExecuteFilter
Refactor the process lifetime inside ExecuteFilter.
Before there was only one process instance during
ExecuteFilter lifetime.
However, the drawback was that when the old task
was still running and we wanted to start a new one,
we had to kill the old one and wait for it to be finished
(so we had to wait unnecessarily). Now we are using
Core::Reaper for removing old running processes.
We also recreate a process for every task and delete it
on task finished.

Fix a possible crash when starting a process times out.
Before we just removed the task from the queue, but it
could happen that we still receive the finished() signal
from the process - and the assumption that task queue
isn't empty inside finished() method didn't hold.
Now we behave the same as in case of ExecuteFilter::accept().
If the process timed out we put it into the reaper.

Fix the ProcessReapers destructor. In case we have put there
a process it won't change its state until we enable event
delivery. So, in order to help it we process events after sleeping.

Task-number: QTCREATORBUG-26084
Change-Id: I8a16b4d64f9c6a07aef45767458e69dff1dd83bc
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
2021-08-04 16:34:43 +00:00

168 lines
4.3 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "reaper.h"
#include "reaper_p.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QProcess>
#include <QThread>
#include <QTimer>
using namespace Utils;
namespace Core {
namespace Internal {
static ProcessReapers *d = nullptr;
class ProcessReaper final : public QObject
{
public:
ProcessReaper(QtcProcess *p, int timeoutMs);
~ProcessReaper() final;
int timeoutMs() const;
bool isFinished() const;
void nextIteration();
private:
mutable QTimer m_iterationTimer;
QtcProcess *m_process;
int m_emergencyCounter = 0;
QProcess::ProcessState m_lastState = QProcess::NotRunning;
};
ProcessReaper::ProcessReaper(QtcProcess *p, int timeoutMs) : m_process(p)
{
d->m_reapers.append(this);
m_iterationTimer.setInterval(timeoutMs);
m_iterationTimer.setSingleShot(true);
connect(&m_iterationTimer, &QTimer::timeout, this, &ProcessReaper::nextIteration);
QMetaObject::invokeMethod(this, &ProcessReaper::nextIteration, Qt::QueuedConnection);
}
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()
{
QProcess::ProcessState state = m_process ? m_process->state() : QProcess::NotRunning;
if (state == QProcess::NotRunning || m_emergencyCounter > 5) {
delete m_process;
m_process = nullptr;
return;
}
if (state == QProcess::Starting) {
if (m_lastState == QProcess::Starting)
m_process->kill();
} else if (state == QProcess::Running) {
if (m_lastState == QProcess::Running)
m_process->kill();
else
m_process->terminate();
}
m_lastState = state;
m_iterationTimer.start();
++m_emergencyCounter;
}
ProcessReapers::ProcessReapers()
{
d = this;
}
ProcessReapers::~ProcessReapers()
{
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);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
alreadyWaited += toSleep;
}
pr->nextIteration();
if (pr->isFinished())
toDelete.append(pr);
}
// Clean out reapers that finished in the meantime
qDeleteAll(toDelete);
toDelete.clear();
}
d = nullptr;
}
} // namespace Internal
namespace Reaper {
void reap(QtcProcess *process, int timeoutMs)
{
if (!process)
return;
QTC_ASSERT(Internal::d, return);
new Internal::ProcessReaper(process, timeoutMs);
}
} // namespace Reaper
} // namespace Core