1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
// Copyright (C) 2024 Jarek Kobus
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef TASKING_QPROCESSTASK_H
#define TASKING_QPROCESSTASK_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "tasking_global.h"
#include "tasktree.h"
#include <QtCore/QProcess>
QT_BEGIN_NAMESPACE
#if QT_CONFIG(process)
namespace Tasking {
// Deleting a running QProcess may block the caller thread up to 30 seconds and issue warnings.
// To avoid these issues we move the running QProcess into a separate thread
// managed by the internal ProcessReaper, instead of deleting it immediately.
// Inside the ProcessReaper's thread we try to finish the process in a most gentle way:
// we call QProcess::terminate() with 500 ms timeout, and if the process is still running
// after this timeout passed, we call QProcess::kill() and wait for the process to finish.
// All these handlings are done is a separate thread, so the main thread doesn't block at all
// when the QProcessTask is destructed.
// Finally, on application quit, QProcessDeleter::deleteAll() should be called in order
// to synchronize all the processes being still potentially reaped in a separate thread.
// The call to QProcessDeleter::deleteAll() is blocking in case some processes
// are still being reaped.
// This strategy seems most sensible, since when passing the running QProcess into the
// ProcessReaper we don't block immediately, but postpone the possible (not certain) block
// until the end of an application.
// In this way we terminate the running processes in the most safe way and keep the main thread
// responsive. That's a common case when the running application wants to terminate the QProcess
// immediately (e.g. on Cancel button pressed), without keeping and managing the handle
// to the still running QProcess.
// The implementation of the internal reaper is inspired by the Utils::ProcessReaper taken
// from the QtCreator codebase.
class TASKING_EXPORT QProcessDeleter
{
public:
// Blocking, should be called after all QProcessAdapter instances are deleted.
static void deleteAll();
void operator()(QProcess *process);
};
class TASKING_EXPORT QProcessAdapter : public TaskAdapter<QProcess, QProcessDeleter>
{
private:
void start() final {
connect(task(), &QProcess::finished, this, [this] {
const bool success = task()->exitStatus() == QProcess::NormalExit
&& task()->error() == QProcess::UnknownError
&& task()->exitCode() == 0;
Q_EMIT done(toDoneResult(success));
});
connect(task(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
if (error != QProcess::FailedToStart)
return;
Q_EMIT done(DoneResult::Error);
});
task()->start();
}
};
using QProcessTask = CustomTask<QProcessAdapter>;
} // namespace Tasking
#endif // QT_CONFIG(process)
QT_END_NAMESPACE
#endif // TASKING_QPROCESSTASK_H
|