diff options
Diffstat (limited to 'src/assets/downloader/tasking/qprocesstask.cpp')
| -rw-r--r-- | src/assets/downloader/tasking/qprocesstask.cpp | 280 |
1 files changed, 0 insertions, 280 deletions
diff --git a/src/assets/downloader/tasking/qprocesstask.cpp b/src/assets/downloader/tasking/qprocesstask.cpp deleted file mode 100644 index a4696ebbbfb..00000000000 --- a/src/assets/downloader/tasking/qprocesstask.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// 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 - -#include "qprocesstask.h" - -#include <QtCore/QCoreApplication> -#include <QtCore/QDebug> -#include <QtCore/QElapsedTimer> -#include <QtCore/QMutex> -#include <QtCore/QThread> -#include <QtCore/QTimer> -#include <QtCore/QWaitCondition> - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(process) - -namespace Tasking { - -class ProcessReaperPrivate; - -class ProcessReaper final -{ -public: - static void reap(QProcess *process, int timeoutMs = 500); - ProcessReaper(); - ~ProcessReaper(); - -private: - static ProcessReaper *instance(); - - QThread m_thread; - ProcessReaperPrivate *m_private; -}; - -static const int s_timeoutThreshold = 10000; // 10 seconds - -static QString execWithArguments(QProcess *process) -{ - QStringList commandLine; - commandLine.append(process->program()); - commandLine.append(process->arguments()); - return commandLine.join(QChar::Space); -} - -struct ReaperSetup -{ - QProcess *m_process = nullptr; - int m_timeoutMs; -}; - -class Reaper : public QObject -{ - Q_OBJECT - -public: - Reaper(const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {} - - void reap() - { - m_timer.start(); - connect(m_reaperSetup.m_process, &QProcess::finished, this, &Reaper::handleFinished); - if (emitFinished()) - return; - terminate(); - } - -Q_SIGNALS: - void finished(); - -private: - void terminate() - { - m_reaperSetup.m_process->terminate(); - 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(); - } - return true; - } - - void handleFinished() - { - if (emitFinished()) - return; - qWarning() << "Finished process still running..."; - // In case the process is still running - wait until it has finished - QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleFinished); - } - - void handleTerminateTimeout() - { - if (emitFinished()) - return; - kill(); - } - - bool m_finished = false; - QElapsedTimer m_timer; - const ReaperSetup m_reaperSetup; -}; - -class ProcessReaperPrivate : public QObject -{ - Q_OBJECT - -public: - // Called from non-reaper's thread - void scheduleReap(const ReaperSetup &reaperSetup) - { - if (QThread::currentThread() == thread()) - qWarning() << "Can't schedule reap from the reaper internal thread."; - - QMutexLocker locker(&m_mutex); - m_reaperSetupList.append(reaperSetup); - QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush); - } - - // Called from non-reaper's thread - void waitForFinished() - { - if (QThread::currentThread() == thread()) - qWarning() << "Can't wait for finished from the reaper internal 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); - return std::exchange(m_reaperSetupList, {}); - } - - void flush() - { - while (true) { - const QList<ReaperSetup> reaperSetupList = takeReaperSetupList(); - if (reaperSetupList.isEmpty()) - return; - for (const ReaperSetup &reaperSetup : reaperSetupList) - reap(reaperSetup); - } - } - - 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); - const bool isRemoved = m_reaperList.removeOne(reaper); - if (!isRemoved) - qWarning() << "Reaper list doesn't contain the finished process."; - - delete reaper; - delete process; - if (m_reaperList.isEmpty()) - m_waitCondition.wakeOne(); - }, Qt::QueuedConnection); - - { - QMutexLocker locker(&m_mutex); - m_reaperList.append(reaper); - } - - reaper->reap(); - } - - QMutex m_mutex; - QWaitCondition m_waitCondition; - QList<ReaperSetup> m_reaperSetupList; - QList<Reaper *> m_reaperList; -}; - -static ProcessReaper *s_instance = nullptr; -static QMutex s_instanceMutex; - -// Call me with s_instanceMutex locked. -ProcessReaper *ProcessReaper::instance() -{ - if (!s_instance) - s_instance = new ProcessReaper; - return s_instance; -} - -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() -{ - if (!QThread::isMainThread()) - qWarning() << "Destructing process reaper from non-main thread."; - - instance()->m_private->waitForFinished(); - m_thread.quit(); - m_thread.wait(); -} - -void ProcessReaper::reap(QProcess *process, int timeoutMs) -{ - if (!process) - return; - - if (QThread::currentThread() != process->thread()) { - qWarning() << "Can't reap process from non-process's thread."; - return; - } - - process->disconnect(); - if (process->state() == QProcess::NotRunning) { - delete process; - return; - } - - // Neither can move object with a parent into a different thread - // nor reaping the process with a parent makes any sense. - process->setParent(nullptr); - - QMutexLocker locker(&s_instanceMutex); - ProcessReaperPrivate *priv = instance()->m_private; - - process->moveToThread(priv->thread()); - ReaperSetup reaperSetup {process, timeoutMs}; - priv->scheduleReap(reaperSetup); -} - -void QProcessDeleter::deleteAll() -{ - QMutexLocker locker(&s_instanceMutex); - delete s_instance; - s_instance = nullptr; -} - -void QProcessDeleter::operator()(QProcess *process) -{ - ProcessReaper::reap(process); -} - -} // namespace Tasking - -#endif // QT_CONFIG(process) - -QT_END_NAMESPACE - -#if QT_CONFIG(process) - -#include "qprocesstask.moc" - -#endif // QT_CONFIG(process) - |
