2016-10-17 12:17:51 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2021-09-06 14:48:08 +02:00
|
|
|
** Copyright (C) 2021 The Qt Company Ltd.
|
2016-10-17 12:17:51 +02:00
|
|
|
** 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.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
#include "processreaper.h"
|
|
|
|
|
#include "qtcassert.h"
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
#include <QCoreApplication>
|
2020-09-11 12:08:49 +02:00
|
|
|
#include <QProcess>
|
2016-11-11 15:02:43 +01:00
|
|
|
#include <QThread>
|
2020-09-11 12:08:49 +02:00
|
|
|
#include <QTimer>
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2021-11-25 20:19:15 +01:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
#ifdef QTCREATOR_PCH_H
|
|
|
|
|
#define CALLBACK WINAPI
|
|
|
|
|
#endif
|
|
|
|
|
#include <qt_windows.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-05-25 16:44:20 +02:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
namespace Utils {
|
2016-10-17 12:17:51 +02:00
|
|
|
namespace Internal {
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
class Reaper final : public QObject
|
2020-09-11 12:08:49 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2021-09-06 14:48:08 +02:00
|
|
|
Reaper(QProcess *p, int timeoutMs);
|
|
|
|
|
~Reaper() final;
|
2020-09-11 12:08:49 +02:00
|
|
|
|
|
|
|
|
int timeoutMs() const;
|
|
|
|
|
bool isFinished() const;
|
|
|
|
|
void nextIteration();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
mutable QTimer m_iterationTimer;
|
2021-09-06 14:48:08 +02:00
|
|
|
QProcess *m_process = nullptr;
|
2020-09-11 12:08:49 +02:00
|
|
|
int m_emergencyCounter = 0;
|
|
|
|
|
QProcess::ProcessState m_lastState = QProcess::NotRunning;
|
|
|
|
|
};
|
2017-02-28 15:31:09 +01:00
|
|
|
|
2021-11-26 18:40:56 +01:00
|
|
|
static QList<Reaper *> g_reapers;
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
Reaper::Reaper(QProcess *p, int timeoutMs) : m_process(p)
|
2016-10-17 12:17:51 +02:00
|
|
|
{
|
2021-11-26 18:40:56 +01:00
|
|
|
g_reapers.append(this);
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2016-10-17 12:17:51 +02:00
|
|
|
m_iterationTimer.setInterval(timeoutMs);
|
|
|
|
|
m_iterationTimer.setSingleShot(true);
|
2021-09-06 14:48:08 +02:00
|
|
|
connect(&m_iterationTimer, &QTimer::timeout, this, &Reaper::nextIteration);
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
QMetaObject::invokeMethod(this, &Reaper::nextIteration, Qt::QueuedConnection);
|
2016-10-17 12:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
Reaper::~Reaper()
|
2016-11-11 15:02:43 +01:00
|
|
|
{
|
2021-11-26 18:40:56 +01:00
|
|
|
g_reapers.removeOne(this);
|
2016-11-11 15:02:43 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
int Reaper::timeoutMs() const
|
2016-11-11 15:02:43 +01:00
|
|
|
{
|
|
|
|
|
const int remaining = m_iterationTimer.remainingTime();
|
|
|
|
|
if (remaining < 0)
|
|
|
|
|
return m_iterationTimer.interval();
|
|
|
|
|
m_iterationTimer.stop();
|
|
|
|
|
return remaining;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
bool Reaper::isFinished() const
|
2016-11-11 15:02:43 +01:00
|
|
|
{
|
|
|
|
|
return !m_process;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-25 20:19:15 +01:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
DWORD dwProcessID;
|
|
|
|
|
GetWindowThreadProcessId(hwnd, &dwProcessID);
|
|
|
|
|
if ((DWORD)lParam == dwProcessID) {
|
|
|
|
|
SendNotifyMessage(hwnd, message, 0, 0);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK sendShutDownMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiShutDownMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown");
|
|
|
|
|
return sendMessage(uiShutDownMessage, hwnd, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
void Reaper::nextIteration()
|
2016-10-17 12:17:51 +02:00
|
|
|
{
|
2016-11-12 22:58:14 +02:00
|
|
|
QProcess::ProcessState state = m_process ? m_process->state() : QProcess::NotRunning;
|
2016-10-17 12:17:51 +02:00
|
|
|
if (state == QProcess::NotRunning || m_emergencyCounter > 5) {
|
|
|
|
|
delete m_process;
|
2016-11-11 15:02:43 +01:00
|
|
|
m_process = nullptr;
|
2016-10-17 12:17:51 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state == QProcess::Starting) {
|
|
|
|
|
if (m_lastState == QProcess::Starting)
|
2021-05-25 16:44:20 +02:00
|
|
|
m_process->kill();
|
2016-10-17 12:17:51 +02:00
|
|
|
} else if (state == QProcess::Running) {
|
2021-11-25 20:19:15 +01:00
|
|
|
if (m_lastState == QProcess::Running) {
|
2021-05-25 16:44:20 +02:00
|
|
|
m_process->kill();
|
2021-11-25 20:19:15 +01:00
|
|
|
} else if (m_process->program().endsWith(QLatin1String("qtcreator_ctrlc_stub.exe"))) {
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
EnumWindows(sendShutDownMessageToAllWindowsOfProcess_enumWnd, m_process->processId());
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
2021-05-25 16:44:20 +02:00
|
|
|
m_process->terminate();
|
2021-11-25 20:19:15 +01:00
|
|
|
}
|
2016-10-17 12:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lastState = state;
|
|
|
|
|
m_iterationTimer.start();
|
|
|
|
|
|
|
|
|
|
++m_emergencyCounter;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
|
|
|
|
|
ProcessReaper::~ProcessReaper()
|
2016-10-17 12:17:51 +02:00
|
|
|
{
|
2021-11-26 18:40:56 +01:00
|
|
|
while (!Internal::g_reapers.isEmpty()) {
|
2016-11-11 15:02:43 +01:00
|
|
|
int alreadyWaited = 0;
|
2021-09-06 14:48:08 +02:00
|
|
|
QList<Internal::Reaper *> toDelete;
|
2016-11-11 15:02:43 +01:00
|
|
|
|
|
|
|
|
// push reapers along:
|
2021-11-26 18:40:56 +01:00
|
|
|
for (Internal::Reaper *pr : qAsConst(Internal::g_reapers)) {
|
2016-11-11 15:02:43 +01:00
|
|
|
const int timeoutMs = pr->timeoutMs();
|
|
|
|
|
if (alreadyWaited < timeoutMs) {
|
|
|
|
|
const unsigned long toSleep = static_cast<unsigned long>(timeoutMs - alreadyWaited);
|
|
|
|
|
QThread::msleep(toSleep);
|
2021-08-04 11:02:51 +02:00
|
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
2016-11-11 15:02:43 +01:00
|
|
|
alreadyWaited += toSleep;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pr->nextIteration();
|
|
|
|
|
|
|
|
|
|
if (pr->isFinished())
|
|
|
|
|
toDelete.append(pr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean out reapers that finished in the meantime
|
|
|
|
|
qDeleteAll(toDelete);
|
|
|
|
|
toDelete.clear();
|
|
|
|
|
}
|
2016-10-17 12:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
void ProcessReaper::reap(QProcess *process, int timeoutMs)
|
2016-10-17 12:17:51 +02:00
|
|
|
{
|
|
|
|
|
if (!process)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-09-20 09:47:45 +02:00
|
|
|
QTC_ASSERT(QThread::currentThread() == process->thread(), return );
|
2021-09-01 14:16:45 +02:00
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
process->disconnect();
|
|
|
|
|
if (process->state() == QProcess::NotRunning) {
|
|
|
|
|
process->deleteLater();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-09-01 14:16:45 +02:00
|
|
|
// Neither can move object with a parent into a different thread
|
|
|
|
|
// nor reaping the process with a parent makes any sense.
|
|
|
|
|
process->setParent(nullptr);
|
2021-09-06 14:48:08 +02:00
|
|
|
if (process->thread() != QCoreApplication::instance()->thread()) {
|
|
|
|
|
process->moveToThread(QCoreApplication::instance()->thread());
|
2021-09-01 14:16:45 +02:00
|
|
|
QMetaObject::invokeMethod(process, [process, timeoutMs] {
|
|
|
|
|
reap(process, timeoutMs);
|
|
|
|
|
}); // will be queued
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
new Internal::Reaper(process, timeoutMs);
|
2016-10-17 12:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
} // namespace Utils
|
2016-10-17 12:17:51 +02:00
|
|
|
|