// SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1 #include "misc/worker.h" #include #include #include #include "misc/logmessage.h" #include "misc/threadutils.h" #include "misc/verify.h" #ifdef Q_OS_WIN32 # include #endif namespace swift::misc { QSet CWorkerBase::s_allWorkers; void CRegularThread::run() { #ifdef Q_OS_WIN32 m_handle = GetCurrentThread(); QThread::run(); m_handle = nullptr; #else QThread::run(); #endif } CRegularThread::~CRegularThread() { const QString name = this->objectName(); #ifdef Q_OS_WIN32 auto handle = m_handle.load(); if (handle) { const auto status = WaitForSingleObject(handle, 0); if (isRunning()) { switch (status) { default: case WAIT_FAILED: qWarning() << "Thread" << name << "unspecified error"; break; case WAIT_OBJECT_0: qWarning() << "Thread" << name << "unsafely terminated by program shutdown"; break; case WAIT_TIMEOUT: break; } } } #endif quit(); // the wait avoids: QThread: Destroyed while thread is still running const unsigned long timeoutMs = 5 * 1000; const bool ok = wait(timeoutMs); //! \todo KB 2017-10 temp workaround: in T145 this will be fixed, sometimes //! (very rarely) hanging here during shutdown const QString as = QStringLiteral("Wait timeout after %1ms for '%2'").arg(timeoutMs).arg(name); const QByteArray asBA = as.toLatin1(); SWIFT_AUDIT_X(ok, Q_FUNC_INFO, asBA); // MS 2018-09 assert because we want a stack trace of all threads, via breakpad Q_UNUSED(ok) } CWorker *CWorker::fromTaskImpl(QObject *owner, const QString &name, int typeId, const std::function &task) { auto *worker = new CWorker(task); emit worker->aboutToStart(); worker->setStarted(); auto *thread = new CRegularThread(owner); if (typeId != QMetaType::Void) { worker->m_result = QVariant(QMetaType(typeId), nullptr); } const QString ownerName = owner->objectName().isEmpty() ? owner->metaObject()->className() : owner->objectName(); thread->setObjectName(ownerName + ":" + name); worker->setObjectName(name); worker->moveToThread(thread); const bool s = QMetaObject::invokeMethod(worker, &CWorker::ps_runTask); Q_ASSERT_X(s, Q_FUNC_INFO, "cannot invoke"); Q_UNUSED(s) thread->start(); return worker; } void CWorker::ps_runTask() { m_result = m_task(); this->setFinished(); QThread *workerThread = this->thread(); Q_ASSERT_X(workerThread->thread()->isRunning(), Q_FUNC_INFO, "Owner thread's event loop already ended"); // MS 2018-09 Now we post the DeferredDelete event from within the worker thread, but rely on it being // dispatched // by the owner thread. Posted events are moved along with the object when moveToThread is called. this->deleteLater(); this->moveToThread( workerThread ->thread()); // move worker back to the thread which constructed it, so there is no race on deletion // must not access the worker beyond this point, as it now lives in the owner's thread and could be deleted at // any moment QMetaObject::invokeMethod(workerThread, [workerThread] { // quit and wait is redundant as the CRegularThread dtor will do that anyway, but put here for debugging workerThread->quit(); const bool ok = workerThread->wait(5000); const QString as = QStringLiteral("Worker thread '%2' refuses to stop after worker finished") .arg(workerThread->objectName()); const QByteArray asBA = as.toLatin1(); SWIFT_AUDIT_X(ok, Q_FUNC_INFO, asBA); workerThread->deleteLater(); }); } CWorkerBase::CWorkerBase() { s_allWorkers.insert(this); } CWorkerBase::~CWorkerBase() { s_allWorkers.remove(this); } const QStringList &CWorkerBase::getLogCategories() { static const QStringList cats { CLogCategories::worker() }; return cats; } void CWorkerBase::waitForFinished() noexcept { std::promise promise; then([&] { promise.set_value(); }); promise.get_future().wait(); } void CWorkerBase::abandon() noexcept { if (thread() != thread()->thread()) { thread()->requestInterruption(); } quit(); } void CWorkerBase::abandonAndWait() noexcept { if (thread() != thread()->thread()) { thread()->requestInterruption(); } quitAndWait(); } bool CWorkerBase::isAbandoned() const { Q_ASSERT(thread() == QThread::currentThread()); return thread()->isInterruptionRequested(); } CContinuousWorker::CContinuousWorker(QObject *owner, const QString &name) : m_owner(owner), m_name(name) { Q_ASSERT_X(!name.isEmpty(), Q_FUNC_INFO, "Empty name"); this->setObjectName(m_name); } void CContinuousWorker::start(QThread::Priority priority) { SWIFT_VERIFY_X(!hasStarted(), Q_FUNC_INFO, "Tried to start a worker that was already started"); if (hasStarted()) { return; } // avoid message "QObject: Cannot create children for a parent that is in a different thread" Q_ASSERT_X(CThreadUtils::isInThisThread(m_owner), Q_FUNC_INFO, "Needs to be started in owner thread"); emit this->aboutToStart(); setStarted(); auto *thread = new CRegularThread(m_owner); Q_ASSERT(m_owner); // must not be null if (m_owner) { const QString ownerName = m_owner->objectName().isEmpty() ? m_owner->metaObject()->className() : m_owner->objectName(); thread->setObjectName(ownerName + ": " + m_name); } moveToThread(thread); connect(thread, &QThread::started, this, &CContinuousWorker::initialize); connect(thread, &QThread::finished, this, &CContinuousWorker::cleanup); connect(thread, &QThread::finished, this, &CContinuousWorker::finish); thread->start(priority); } void CContinuousWorker::quit() noexcept { this->setEnabled(false); // already in owner's thread? then return if (this->thread() == m_owner->thread()) { return; } // remark: cannot stop timer here, as I am normally not in the correct thread this->beforeQuit(); thread()->quit(); } void CContinuousWorker::quitAndWait() noexcept { this->setEnabled(false); // already in owner's thread? then return if (this->thread() == m_owner->thread()) { return; } // called by own thread, will deadlock, return if (CThreadUtils::isInThisThread(this)) { return; } QThread *workerThread = thread(); // must be before quit() this->quit(); // T647, discussed here: // https://discordapp.com/channels/539048679160676382/539925070550794240/573260844004016148 const unsigned long waitTimeoutMs = this->waitTimeoutMs(); const QString name(this->getName()); qint64 waitTime = QDateTime::currentMSecsSinceEpoch(); const bool ok = workerThread->wait(waitTimeoutMs); waitTime = QDateTime::currentMSecsSinceEpoch() - waitTime; const QString msg = QStringLiteral("Waiting for quitAndWait of '%1' for %2ms").arg(name).arg(waitTime); const QByteArray msgBA = msg.toLatin1(); SWIFT_AUDIT_X(ok, Q_FUNC_INFO, msgBA); // MS 2019-05 AUDIT because we want a stack trace of all threads, via breakpad CLogMessage(this).info(msg); Q_UNUSED(ok) } void CContinuousWorker::finish() { this->setFinished(); QThread *workerThread = this->thread(); Q_ASSERT_X(m_owner->thread()->isRunning(), Q_FUNC_INFO, "Owner thread's event loop already ended"); // MS 2018-09 Now we post the DeferredDelete event from within the worker thread, but rely on it being // dispatched // by the owner thread. Posted events are moved along with the object when moveToThread is called. this->deleteLater(); this->moveToThread( m_owner->thread()); // move worker back to the thread which constructed it, so there is no race on deletion // must not access the worker beyond this point, as it now lives in the owner's thread and could be deleted at // any moment QMetaObject::invokeMethod(workerThread, [workerThread] { // quit and wait is redundant as the CRegularThread dtor will do that anyway, but put here for debugging workerThread->quit(); const bool ok = workerThread->wait(5000); const QString as = QStringLiteral("Worker thread '%2' refuses to stop after worker finished") .arg(workerThread->objectName()); const QByteArray asBA = as.toLatin1(); SWIFT_AUDIT_X(ok, Q_FUNC_INFO, asBA); workerThread->deleteLater(); }); } } // namespace swift::misc