Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qprocesstask.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 Jarek Kobus
2// Copyright (C) 2024 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qprocesstask.h"
6
7#include <QtCore/QCoreApplication>
8#include <QtCore/QDebug>
9#include <QtCore/QMutex>
10#include <QtCore/QThread>
11#include <QtCore/QTimer>
12#include <QtCore/QWaitCondition>
13
15
16#if QT_CONFIG(process)
17
18namespace Tasking {
19
20class ProcessReaperPrivate;
21
22class ProcessReaper final
23{
24public:
25 static void reap(QProcess *process, int timeoutMs = 500);
26 ProcessReaper();
27 ~ProcessReaper();
28
29private:
30 static ProcessReaper *instance();
31
32 QThread m_thread;
33 ProcessReaperPrivate *m_private;
34};
35
36static const int s_timeoutThreshold = 10000; // 10 seconds
37
38static QString execWithArguments(QProcess *process)
39{
40 QStringList commandLine;
41 commandLine.append(process->program());
42 commandLine.append(process->arguments());
43 return commandLine.join(QChar::Space);
44}
45
46struct ReaperSetup
47{
48 QProcess *m_process = nullptr;
49 int m_timeoutMs;
50};
51
52class Reaper : public QObject
53{
55
56public:
57 Reaper(const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {}
58
59 void reap()
60 {
61 m_timer.start();
62 connect(m_reaperSetup.m_process, &QProcess::finished, this, &Reaper::handleFinished);
63 if (emitFinished())
64 return;
65 terminate();
66 }
67
69 void finished();
70
71private:
72 void terminate()
73 {
74 m_reaperSetup.m_process->terminate();
75 QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleTerminateTimeout);
76 }
77
78 void kill() { m_reaperSetup.m_process->kill(); }
79
80 bool emitFinished()
81 {
82 if (m_reaperSetup.m_process->state() != QProcess::NotRunning)
83 return false;
84
85 if (!m_finished) {
86 const int timeout = m_timer.elapsed();
87 if (timeout > s_timeoutThreshold) {
88 qWarning() << "Finished parallel reaping of" << execWithArguments(m_reaperSetup.m_process)
89 << "in" << (timeout / 1000.0) << "seconds.";
90 }
91
92 m_finished = true;
93 emit finished();
94 }
95 return true;
96 }
97
98 void handleFinished()
99 {
100 if (emitFinished())
101 return;
102 qWarning() << "Finished process still running...";
103 // In case the process is still running - wait until it has finished
104 QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleFinished);
105 }
106
107 void handleTerminateTimeout()
108 {
109 if (emitFinished())
110 return;
111 kill();
112 }
113
114 bool m_finished = false;
115 QElapsedTimer m_timer;
116 const ReaperSetup m_reaperSetup;
117};
118
119class ProcessReaperPrivate : public QObject
120{
122
123public:
124 // Called from non-reaper's thread
125 void scheduleReap(const ReaperSetup &reaperSetup)
126 {
127 if (QThread::currentThread() == thread())
128 qWarning() << "Can't schedule reap from the reaper internal thread.";
129
130 QMutexLocker locker(&m_mutex);
131 m_reaperSetupList.append(reaperSetup);
132 QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush);
133 }
134
135 // Called from non-reaper's thread
136 void waitForFinished()
137 {
138 if (QThread::currentThread() == thread())
139 qWarning() << "Can't wait for finished from the reaper internal thread.";
140
141 QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush,
143 QMutexLocker locker(&m_mutex);
144 if (m_reaperList.isEmpty())
145 return;
146
147 m_waitCondition.wait(&m_mutex);
148 }
149
150private:
151 // All the private methods are called from the reaper's thread
152 QList<ReaperSetup> takeReaperSetupList()
153 {
154 QMutexLocker locker(&m_mutex);
155 return std::exchange(m_reaperSetupList, {});
156 }
157
158 void flush()
159 {
160 while (true) {
161 const QList<ReaperSetup> reaperSetupList = takeReaperSetupList();
162 if (reaperSetupList.isEmpty())
163 return;
164 for (const ReaperSetup &reaperSetup : reaperSetupList)
165 reap(reaperSetup);
166 }
167 }
168
169 void reap(const ReaperSetup &reaperSetup)
170 {
171 Reaper *reaper = new Reaper(reaperSetup);
172 connect(reaper, &Reaper::finished, this, [this, reaper, process = reaperSetup.m_process] {
173 QMutexLocker locker(&m_mutex);
174 const bool isRemoved = m_reaperList.removeOne(reaper);
175 if (!isRemoved)
176 qWarning() << "Reaper list doesn't contain the finished process.";
177
178 delete reaper;
179 delete process;
180 if (m_reaperList.isEmpty())
181 m_waitCondition.wakeOne();
183
184 {
185 QMutexLocker locker(&m_mutex);
186 m_reaperList.append(reaper);
187 }
188
189 reaper->reap();
190 }
191
192 QMutex m_mutex;
193 QWaitCondition m_waitCondition;
194 QList<ReaperSetup> m_reaperSetupList;
195 QList<Reaper *> m_reaperList;
196};
197
198static ProcessReaper *s_instance = nullptr;
199static QMutex s_instanceMutex;
200
201// Call me with s_instanceMutex locked.
202ProcessReaper *ProcessReaper::instance()
203{
204 if (!s_instance)
205 s_instance = new ProcessReaper;
206 return s_instance;
207}
208
209ProcessReaper::ProcessReaper()
210 : m_private(new ProcessReaperPrivate)
211{
212 m_private->moveToThread(&m_thread);
214 m_thread.start();
215 m_thread.moveToThread(qApp->thread());
216}
217
218ProcessReaper::~ProcessReaper()
219{
220 if (QThread::currentThread() != qApp->thread())
221 qWarning() << "Destructing process reaper from non-main thread.";
222
223 instance()->m_private->waitForFinished();
224 m_thread.quit();
225 m_thread.wait();
226}
227
228void ProcessReaper::reap(QProcess *process, int timeoutMs)
229{
230 if (!process)
231 return;
232
233 if (QThread::currentThread() != process->thread()) {
234 qWarning() << "Can't reap process from non-process's thread.";
235 return;
236 }
237
238 process->disconnect();
239 if (process->state() == QProcess::NotRunning) {
240 delete process;
241 return;
242 }
243
244 // Neither can move object with a parent into a different thread
245 // nor reaping the process with a parent makes any sense.
246 process->setParent(nullptr);
247
248 QMutexLocker locker(&s_instanceMutex);
249 ProcessReaperPrivate *priv = instance()->m_private;
250
251 process->moveToThread(priv->thread());
252 ReaperSetup reaperSetup {process, timeoutMs};
253 priv->scheduleReap(reaperSetup);
254}
255
256void QProcessDeleter::deleteAll()
257{
258 QMutexLocker locker(&s_instanceMutex);
259 delete s_instance;
260 s_instance = nullptr;
261}
262
263void QProcessDeleter::operator()(QProcess *process)
264{
265 ProcessReaper::reap(process);
266}
267
268} // namespace Tasking
269
270#endif // QT_CONFIG(process)
271
273
274#if QT_CONFIG(process)
275
276#include "qprocesstask.moc"
277
278#endif // QT_CONFIG(process)
279
\inmodule QtCore
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qmutex.h:281
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QThread * currentThread()
Definition qthread.cpp:1039
void finished(QPrivateSignal)
bool singleShot
whether the timer is a single-shot timer
Definition qtimer.h:22
Combined button and popup list for selecting options.
QTextStream & flush(QTextStream &stream)
Calls QTextStream::flush() on stream and returns stream.
@ BlockingQueuedConnection
@ QueuedConnection
\inmodule TaskingSolution
Definition barrier.cpp:9
#define qApp
#define qWarning
Definition qlogging.h:167
static const QMetaObjectPrivate * priv(const uint *data)
GLbitfield GLuint64 timeout
[4]
#define Q_OBJECT
#define Q_SIGNALS
#define emit
future waitForFinished()
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...