7#include <QtCore/QCoreApplication>
8#include <QtCore/QDebug>
9#include <QtCore/QElapsedTimer>
10#include <QtCore/QMutex>
11#include <QtCore/QThread>
12#include <QtCore/QTimer>
13#include <QtCore/QWaitCondition>
21class ProcessReaperPrivate;
23class ProcessReaper final
26 static void reap(QProcess *process,
int timeoutMs = 500);
31 static ProcessReaper *instance();
34 ProcessReaperPrivate *m_private;
37static const int s_timeoutThreshold = 10000;
39static QString execWithArguments(QProcess *process)
41 QStringList commandLine;
42 commandLine.append(process->program());
43 commandLine.append(process->arguments());
44 return commandLine.join(QChar::Space);
49 QProcess *m_process =
nullptr;
53class Reaper :
public QObject
58 Reaper(
const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {}
63 connect(m_reaperSetup.m_process, &QProcess::finished,
this, &Reaper::handleFinished);
75 m_reaperSetup.m_process->terminate();
76 QTimer::singleShot(m_reaperSetup.m_timeoutMs,
this, &Reaper::handleTerminateTimeout);
79 void kill() { m_reaperSetup.m_process->kill(); }
83 if (m_reaperSetup.m_process->state() != QProcess::NotRunning)
87 const int timeout = m_timer.elapsed();
88 if (timeout > s_timeoutThreshold) {
89 qWarning() <<
"Finished parallel reaping of" << execWithArguments(m_reaperSetup.m_process)
90 <<
"in" << (timeout / 1000.0) <<
"seconds.";
103 qWarning() <<
"Finished process still running...";
105 QTimer::singleShot(m_reaperSetup.m_timeoutMs,
this, &Reaper::handleFinished);
108 void handleTerminateTimeout()
115 bool m_finished =
false;
116 QElapsedTimer m_timer;
117 const ReaperSetup m_reaperSetup;
120class ProcessReaperPrivate :
public QObject
126 void scheduleReap(
const ReaperSetup &reaperSetup)
128 if (QThread::currentThread() == thread())
129 qWarning() <<
"Can't schedule reap from the reaper internal thread.";
131 QMutexLocker locker(&m_mutex);
132 m_reaperSetupList.append(reaperSetup);
133 QMetaObject::invokeMethod(
this, &ProcessReaperPrivate::flush);
137 void waitForFinished()
139 if (QThread::currentThread() == thread())
140 qWarning() <<
"Can't wait for finished from the reaper internal thread.";
142 QMetaObject::invokeMethod(
this, &ProcessReaperPrivate::flush,
143 Qt::BlockingQueuedConnection);
144 QMutexLocker locker(&m_mutex);
145 if (m_reaperList.isEmpty())
148 m_waitCondition.wait(&m_mutex);
153 QList<ReaperSetup> takeReaperSetupList()
155 QMutexLocker locker(&m_mutex);
156 return std::exchange(m_reaperSetupList, {});
162 const QList<ReaperSetup> reaperSetupList = takeReaperSetupList();
163 if (reaperSetupList.isEmpty())
165 for (
const ReaperSetup &reaperSetup : reaperSetupList)
170 void reap(
const ReaperSetup &reaperSetup)
172 Reaper *reaper =
new Reaper(reaperSetup);
173 connect(reaper, &Reaper::finished,
this, [
this, reaper, process = reaperSetup.m_process] {
174 QMutexLocker locker(&m_mutex);
175 const bool isRemoved = m_reaperList.removeOne(reaper);
177 qWarning() <<
"Reaper list doesn't contain the finished process.";
181 if (m_reaperList.isEmpty())
182 m_waitCondition.wakeOne();
183 }, Qt::QueuedConnection);
186 QMutexLocker locker(&m_mutex);
187 m_reaperList.append(reaper);
194 QWaitCondition m_waitCondition;
195 QList<ReaperSetup> m_reaperSetupList;
196 QList<Reaper *> m_reaperList;
199static ProcessReaper *s_instance =
nullptr;
200static QMutex s_instanceMutex;
203ProcessReaper *ProcessReaper::instance()
206 s_instance =
new ProcessReaper;
210ProcessReaper::ProcessReaper()
211 : m_private(
new ProcessReaperPrivate)
213 m_private->moveToThread(&m_thread);
214 QObject::connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater);
216 m_thread.moveToThread(qApp->thread());
219ProcessReaper::~ProcessReaper()
221 if (!QThread::isMainThread())
222 qWarning() <<
"Destructing process reaper from non-main thread.";
224 instance()->m_private->waitForFinished();
229void ProcessReaper::reap(QProcess *process,
int timeoutMs)
234 if (QThread::currentThread() != process->thread()) {
235 qWarning() <<
"Can't reap process from non-process's thread.";
239 process->disconnect();
240 if (process->state() == QProcess::NotRunning) {
247 process->setParent(
nullptr);
249 QMutexLocker locker(&s_instanceMutex);
250 ProcessReaperPrivate *priv = instance()->m_private;
252 process->moveToThread(priv->thread());
253 ReaperSetup reaperSetup {process, timeoutMs};
254 priv->scheduleReap(reaperSetup);
257void QProcessDeleter::deleteAll()
259 QMutexLocker locker(&s_instanceMutex);
261 s_instance =
nullptr;
264void QProcessDeleter::operator()(QProcess *process)
266 ProcessReaper::reap(process);
275#if QT_CONFIG(process)
277#include "qprocesstask.moc"