summaryrefslogtreecommitdiffstats
path: root/src/assets/downloader/tasking/qprocesstask.h
blob: 6f2ca4a18e2455be5e55bcade8d221476348250a (plain)
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