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"
|
2022-03-14 17:02:11 +01:00
|
|
|
#include "processutils.h"
|
2021-09-06 14:48:08 +02:00
|
|
|
#include "qtcassert.h"
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
#include <QCoreApplication>
|
2022-03-28 15:23:36 +02:00
|
|
|
#include <QDebug>
|
2022-03-10 18:06:38 +01:00
|
|
|
#include <QElapsedTimer>
|
2020-09-11 12:08:49 +02:00
|
|
|
#include <QProcess>
|
|
|
|
|
#include <QTimer>
|
2022-03-10 18:06:38 +01:00
|
|
|
#include <QWaitCondition>
|
2021-11-25 20:19:15 +01:00
|
|
|
|
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 {
|
|
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
/*
|
2020-09-11 12:08:49 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
Observations on how QProcess::terminate() behaves on different platforms when called for
|
|
|
|
|
never ending running process:
|
2020-09-11 12:08:49 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
1. Windows:
|
2017-02-28 15:31:09 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
The issue in QTCREATORBUG-27118 with adb.exe is most probably a special
|
|
|
|
|
Windows only case described in docs of QProcess::terminate():
|
2021-11-26 18:40:56 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
"Console applications on Windows that do not run an event loop, or whose event loop does
|
|
|
|
|
not handle the WM_CLOSE message, can only be terminated by calling kill()."
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
It looks like when you call terminate() for the adb.exe, it won't stop, never, even after
|
|
|
|
|
default 30 seconds timeout. The same happens for blocking processes tested in
|
|
|
|
|
tst_QtcProcess::killBlockingProcess(). It's hard to say whether any process on Windows can
|
|
|
|
|
be finished by a call to terminate(). Until now, no such a process has been found.
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
Further call to kill() (after a call to terminate()) finishes the process quickly.
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
2. Linux:
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
It looks like a call to terminate() finishes the running process after a long wait
|
|
|
|
|
(between 10-15 seconds). After calling terminate(), further calls to kill() doesn't
|
|
|
|
|
make the process to finish soon (are no-op). On the other hand, when we start reaping the
|
|
|
|
|
running process from a call to kill() without a prior call to terminate(), the process
|
|
|
|
|
finishes quickly.
|
|
|
|
|
|
|
|
|
|
3. Mac:
|
|
|
|
|
|
|
|
|
|
It looks like the process finishes quickly after a call to terminate().
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static const int s_timeoutThreshold = 10000; // 10 seconds
|
|
|
|
|
|
|
|
|
|
static QString execWithArguments(QProcess *process)
|
2016-11-11 15:02:43 +01:00
|
|
|
{
|
2022-03-10 18:06:38 +01:00
|
|
|
QStringList commandLine;
|
|
|
|
|
commandLine.append(process->program());
|
|
|
|
|
commandLine.append(process->arguments());
|
|
|
|
|
return commandLine.join(QChar::Space);
|
2016-11-11 15:02:43 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
struct ReaperSetup
|
2021-11-25 20:19:15 +01:00
|
|
|
{
|
2022-03-10 18:06:38 +01:00
|
|
|
QProcess *m_process = nullptr;
|
|
|
|
|
int m_timeoutMs;
|
|
|
|
|
};
|
2021-11-25 20:19:15 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
class Reaper : public QObject
|
2021-11-25 20:19:15 +01:00
|
|
|
{
|
2022-03-10 18:06:38 +01:00
|
|
|
Q_OBJECT
|
2021-11-25 20:19:15 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
public:
|
|
|
|
|
Reaper(const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {}
|
2021-11-25 20:19:15 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
void reap()
|
|
|
|
|
{
|
|
|
|
|
m_timer.start();
|
|
|
|
|
|
2022-03-28 14:17:31 +02:00
|
|
|
connect(m_reaperSetup.m_process,
|
|
|
|
|
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
2022-03-10 18:06:38 +01:00
|
|
|
this, &Reaper::handleFinished);
|
|
|
|
|
|
|
|
|
|
if (emitFinished())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
terminate();
|
2016-10-17 12:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
signals:
|
|
|
|
|
void finished();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void terminate()
|
|
|
|
|
{
|
2022-03-14 17:02:11 +01:00
|
|
|
ProcessHelper::terminateProcess(m_reaperSetup.m_process);
|
2022-03-10 18:06:38 +01:00
|
|
|
QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleTerminateTimeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void kill() { m_reaperSetup.m_process->kill(); }
|
|
|
|
|
|
|
|
|
|
bool emitFinished()
|
|
|
|
|
{
|
|
|
|
|
if (m_reaperSetup.m_process->state() != QProcess::NotRunning)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!m_finished) {
|
|
|
|
|
const int timeout = m_timer.elapsed();
|
|
|
|
|
if (timeout > s_timeoutThreshold) {
|
|
|
|
|
qWarning() << "Finished parallel reaping of" << execWithArguments(m_reaperSetup.m_process)
|
|
|
|
|
<< "in" << (timeout / 1000.0) << "seconds.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_finished = true;
|
|
|
|
|
emit finished();
|
2021-11-25 20:19:15 +01:00
|
|
|
}
|
2022-03-10 18:06:38 +01:00
|
|
|
return true;
|
2016-10-17 12:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
void handleFinished()
|
|
|
|
|
{
|
|
|
|
|
// In case the process is still running - wait until it has finished
|
|
|
|
|
QTC_ASSERT(emitFinished(), QTimer::singleShot(m_reaperSetup.m_timeoutMs,
|
|
|
|
|
this, &Reaper::handleFinished));
|
|
|
|
|
}
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
void handleTerminateTimeout()
|
|
|
|
|
{
|
|
|
|
|
if (emitFinished())
|
|
|
|
|
return;
|
2016-10-17 12:17:51 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
kill();
|
|
|
|
|
}
|
2021-09-06 14:48:08 +02:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
bool m_finished = false;
|
|
|
|
|
QElapsedTimer m_timer;
|
|
|
|
|
const ReaperSetup m_reaperSetup;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ProcessReaperPrivate : public QObject
|
2016-10-17 12:17:51 +02:00
|
|
|
{
|
2022-03-10 18:06:38 +01:00
|
|
|
Q_OBJECT
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
public:
|
|
|
|
|
// Called from non-reaper's thread
|
|
|
|
|
void scheduleReap(const ReaperSetup &reaperSetup)
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(QThread::currentThread() != thread());
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_reaperSetupList.append(reaperSetup);
|
|
|
|
|
QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from non-reaper's thread
|
|
|
|
|
void waitForFinished()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(QThread::currentThread() != thread());
|
|
|
|
|
QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush,
|
|
|
|
|
Qt::BlockingQueuedConnection);
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
if (m_reaperList.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_waitCondition.wait(&m_mutex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// All the private methods are called from the reaper's thread
|
|
|
|
|
QList<ReaperSetup> takeReaperSetupList()
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
const QList<ReaperSetup> reaperSetupList = m_reaperSetupList;
|
|
|
|
|
m_reaperSetupList.clear();
|
|
|
|
|
return reaperSetupList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void flush()
|
|
|
|
|
{
|
|
|
|
|
while (true) {
|
|
|
|
|
const QList<ReaperSetup> reaperSetupList = takeReaperSetupList();
|
|
|
|
|
if (reaperSetupList.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
for (const ReaperSetup &reaperSetup : reaperSetupList)
|
|
|
|
|
reap(reaperSetup);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-11 15:02:43 +01:00
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
void reap(const ReaperSetup &reaperSetup)
|
|
|
|
|
{
|
|
|
|
|
Reaper *reaper = new Reaper(reaperSetup);
|
|
|
|
|
connect(reaper, &Reaper::finished, this, [this, reaper, process = reaperSetup.m_process] {
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
QTC_CHECK(m_reaperList.removeOne(reaper));
|
|
|
|
|
delete reaper;
|
|
|
|
|
delete process;
|
|
|
|
|
if (m_reaperList.isEmpty())
|
|
|
|
|
m_waitCondition.wakeOne();
|
|
|
|
|
}, Qt::QueuedConnection);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_reaperList.append(reaper);
|
2016-11-11 15:02:43 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
reaper->reap();
|
2016-11-11 15:02:43 +01:00
|
|
|
}
|
2022-03-10 18:06:38 +01:00
|
|
|
|
|
|
|
|
QMutex m_mutex;
|
|
|
|
|
QWaitCondition m_waitCondition;
|
|
|
|
|
QList<ReaperSetup> m_reaperSetupList;
|
|
|
|
|
QList<Reaper *> m_reaperList;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
|
|
|
|
|
using namespace Utils::Internal;
|
|
|
|
|
|
|
|
|
|
static QMutex s_instanceMutex;
|
|
|
|
|
|
|
|
|
|
ProcessReaper::ProcessReaper()
|
|
|
|
|
: m_private(new ProcessReaperPrivate())
|
|
|
|
|
{
|
|
|
|
|
m_private->moveToThread(&m_thread);
|
|
|
|
|
QObject::connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater);
|
|
|
|
|
m_thread.start();
|
|
|
|
|
m_thread.moveToThread(qApp->thread());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProcessReaper::~ProcessReaper()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(QThread::currentThread() == qApp->thread());
|
|
|
|
|
QMutexLocker locker(&s_instanceMutex);
|
|
|
|
|
instance()->m_private->waitForFinished();
|
|
|
|
|
m_thread.quit();
|
|
|
|
|
m_thread.wait();
|
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;
|
|
|
|
|
|
2022-03-10 18:06:38 +01: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;
|
|
|
|
|
}
|
2022-03-10 18:06:38 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
QMutexLocker locker(&s_instanceMutex);
|
|
|
|
|
ProcessReaperPrivate *priv = instance()->m_private;
|
|
|
|
|
|
|
|
|
|
process->moveToThread(priv->thread());
|
|
|
|
|
ReaperSetup reaperSetup {process, timeoutMs};
|
|
|
|
|
priv->scheduleReap(reaperSetup);
|
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
|
|
|
|
2022-03-10 18:06:38 +01:00
|
|
|
#include "processreaper.moc"
|