swift
worker.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "misc/worker.h"
5 
6 #include <future>
7 
8 #include <QPointer>
9 #include <QTimer>
10 
11 #include "misc/logmessage.h"
12 #include "misc/threadutils.h"
13 #include "misc/verify.h"
14 
15 #ifdef Q_OS_WIN32
16 # include <Windows.h>
17 #endif
18 
19 namespace swift::misc
20 {
21  QSet<CWorkerBase *> CWorkerBase::s_allWorkers;
22 
24  {
25 #ifdef Q_OS_WIN32
26  m_handle = GetCurrentThread();
27  QThread::run();
28  m_handle = nullptr;
29 #else
30  QThread::run();
31 #endif
32  }
33 
35  {
36  const QString name = this->objectName();
37 
38 #ifdef Q_OS_WIN32
39  auto handle = m_handle.load();
40  if (handle)
41  {
42  const auto status = WaitForSingleObject(handle, 0);
43  if (isRunning())
44  {
45  switch (status)
46  {
47  default:
48  case WAIT_FAILED: qWarning() << "Thread" << name << "unspecified error"; break;
49  case WAIT_OBJECT_0: qWarning() << "Thread" << name << "unsafely terminated by program shutdown"; break;
50  case WAIT_TIMEOUT: break;
51  }
52  }
53  }
54 #endif
55  quit();
56 
57  // the wait avoids: QThread: Destroyed while thread is still running
58  const unsigned long timeoutMs = 5 * 1000;
59  const bool ok = wait(timeoutMs);
61  const QString as = QStringLiteral("Wait timeout after %1ms for '%2'").arg(timeoutMs).arg(name);
62  const QByteArray asBA = as.toLatin1();
63  SWIFT_AUDIT_X(ok, Q_FUNC_INFO,
64  asBA); // MS 2018-09 assert because we want a stack trace of all threads, via breakpad
65  Q_UNUSED(ok)
66  }
67 
68  CWorker *CWorker::fromTaskImpl(QObject *owner, const QString &name, int typeId,
69  const std::function<QVariant()> &task)
70  {
71  auto *worker = new CWorker(task);
72  emit worker->aboutToStart();
73  worker->setStarted();
74  auto *thread = new CRegularThread(owner);
75 
76  if (typeId != QMetaType::Void) { worker->m_result = QVariant(QMetaType(typeId), nullptr); }
77 
78  const QString ownerName =
79  owner->objectName().isEmpty() ? owner->metaObject()->className() : owner->objectName();
80  thread->setObjectName(ownerName + ":" + name);
81  worker->setObjectName(name);
82 
83  worker->moveToThread(thread);
84  const bool s = QMetaObject::invokeMethod(worker, &CWorker::ps_runTask);
85  Q_ASSERT_X(s, Q_FUNC_INFO, "cannot invoke");
86  Q_UNUSED(s)
87  thread->start();
88  return worker;
89  }
90 
91  void CWorker::ps_runTask()
92  {
93  m_result = m_task();
94 
95  this->setFinished();
96 
97  QThread *workerThread = this->thread();
98  Q_ASSERT_X(workerThread->thread()->isRunning(), Q_FUNC_INFO, "Owner thread's event loop already ended");
99 
100  // MS 2018-09 Now we post the DeferredDelete event from within the worker thread, but rely on it being
101  // dispatched
102  // by the owner thread. Posted events are moved along with the object when moveToThread is called.
103  this->deleteLater();
104 
105  this->moveToThread(
106  workerThread
107  ->thread()); // move worker back to the thread which constructed it, so there is no race on deletion
108  // must not access the worker beyond this point, as it now lives in the owner's thread and could be deleted at
109  // any moment
110 
111  QMetaObject::invokeMethod(workerThread, [workerThread] {
112  // quit and wait is redundant as the CRegularThread dtor will do that anyway, but put here for debugging
113  workerThread->quit();
114  const bool ok = workerThread->wait(5000);
115  const QString as = QStringLiteral("Worker thread '%2' refuses to stop after worker finished")
116  .arg(workerThread->objectName());
117  const QByteArray asBA = as.toLatin1();
118  SWIFT_AUDIT_X(ok, Q_FUNC_INFO, asBA);
119 
120  workerThread->deleteLater();
121  });
122  }
123 
124  CWorkerBase::CWorkerBase() { s_allWorkers.insert(this); }
125 
126  CWorkerBase::~CWorkerBase() { s_allWorkers.remove(this); }
127 
128  const QStringList &CWorkerBase::getLogCategories()
129  {
130  static const QStringList cats { CLogCategories::worker() };
131  return cats;
132  }
133 
135  {
136  std::promise<void> promise;
137  then([&] { promise.set_value(); });
138  promise.get_future().wait();
139  }
140 
141  void CWorkerBase::abandon() noexcept
142  {
143  if (thread() != thread()->thread()) { thread()->requestInterruption(); }
144  quit();
145  }
146 
148  {
149  if (thread() != thread()->thread()) { thread()->requestInterruption(); }
150  quitAndWait();
151  }
152 
154  {
155  Q_ASSERT(thread() == QThread::currentThread());
156  return thread()->isInterruptionRequested();
157  }
158 
159  CContinuousWorker::CContinuousWorker(QObject *owner, const QString &name) : m_owner(owner), m_name(name)
160  {
161  Q_ASSERT_X(!name.isEmpty(), Q_FUNC_INFO, "Empty name");
162  this->setObjectName(m_name);
163  }
164 
165  void CContinuousWorker::start(QThread::Priority priority)
166  {
167  SWIFT_VERIFY_X(!hasStarted(), Q_FUNC_INFO, "Tried to start a worker that was already started");
168  if (hasStarted()) { return; }
169 
170  // avoid message "QObject: Cannot create children for a parent that is in a different thread"
171  Q_ASSERT_X(CThreadUtils::isInThisThread(m_owner), Q_FUNC_INFO, "Needs to be started in owner thread");
172  emit this->aboutToStart();
173  setStarted();
174  auto *thread = new CRegularThread(m_owner);
175 
176  Q_ASSERT(m_owner); // must not be null
177  if (m_owner)
178  {
179  const QString ownerName =
180  m_owner->objectName().isEmpty() ? m_owner->metaObject()->className() : m_owner->objectName();
181  thread->setObjectName(ownerName + ": " + m_name);
182  }
183 
184  moveToThread(thread);
185  connect(thread, &QThread::started, this, &CContinuousWorker::initialize);
186  connect(thread, &QThread::finished, this, &CContinuousWorker::cleanup);
187  connect(thread, &QThread::finished, this, &CContinuousWorker::finish);
188  thread->start(priority);
189  }
190 
191  void CContinuousWorker::quit() noexcept
192  {
193  this->setEnabled(false);
194 
195  // already in owner's thread? then return
196  if (this->thread() == m_owner->thread()) { return; }
197 
198  // remark: cannot stop timer here, as I am normally not in the correct thread
199  this->beforeQuit();
200  thread()->quit();
201  }
202 
204  {
205  this->setEnabled(false);
206 
207  // already in owner's thread? then return
208  if (this->thread() == m_owner->thread()) { return; }
209 
210  // called by own thread, will deadlock, return
211  if (CThreadUtils::isInThisThread(this)) { return; }
212 
213  QThread *workerThread = thread(); // must be before quit()
214  this->quit();
215 
216  // T647, discussed here:
217  // https://discordapp.com/channels/539048679160676382/539925070550794240/573260844004016148
218  const unsigned long waitTimeoutMs = this->waitTimeoutMs();
219  const QString name(this->getName());
220  qint64 waitTime = QDateTime::currentMSecsSinceEpoch();
221  const bool ok = workerThread->wait(waitTimeoutMs);
222  waitTime = QDateTime::currentMSecsSinceEpoch() - waitTime;
223  const QString msg = QStringLiteral("Waiting for quitAndWait of '%1' for %2ms").arg(name).arg(waitTime);
224  const QByteArray msgBA = msg.toLatin1();
225  SWIFT_AUDIT_X(ok, Q_FUNC_INFO,
226  msgBA); // MS 2019-05 AUDIT because we want a stack trace of all threads, via breakpad
227  CLogMessage(this).info(msg);
228  Q_UNUSED(ok)
229  }
230 
231  void CContinuousWorker::finish()
232  {
233  this->setFinished();
234 
235  QThread *workerThread = this->thread();
236  Q_ASSERT_X(m_owner->thread()->isRunning(), Q_FUNC_INFO, "Owner thread's event loop already ended");
237 
238  // MS 2018-09 Now we post the DeferredDelete event from within the worker thread, but rely on it being
239  // dispatched
240  // by the owner thread. Posted events are moved along with the object when moveToThread is called.
241  this->deleteLater();
242 
243  this->moveToThread(
244  m_owner->thread()); // move worker back to the thread which constructed it, so there is no race on deletion
245  // must not access the worker beyond this point, as it now lives in the owner's thread and could be deleted at
246  // any moment
247 
248  QMetaObject::invokeMethod(workerThread, [workerThread] {
249  // quit and wait is redundant as the CRegularThread dtor will do that anyway, but put here for debugging
250  workerThread->quit();
251  const bool ok = workerThread->wait(5000);
252  const QString as = QStringLiteral("Worker thread '%2' refuses to stop after worker finished")
253  .arg(workerThread->objectName());
254  const QByteArray asBA = as.toLatin1();
255  SWIFT_AUDIT_X(ok, Q_FUNC_INFO, asBA);
256 
257  workerThread->deleteLater();
258  });
259  }
260 } // namespace swift::misc
virtual void beforeQuit() noexcept
Called before quit is called.
Definition: worker.h:341
virtual void cleanup()
Called when the thread is finished.
Definition: worker.h:337
void quit() noexcept final
Stops the thread the next time around its event loop. The thread and the worker will then be deleted.
Definition: worker.cpp:191
virtual void initialize()
Called when the thread is started.
Definition: worker.h:334
virtual unsigned long waitTimeoutMs() const
Wait time for quitAndWait, 0 means not waiting.
Definition: worker.h:344
const QString & getName()
Name of the worker.
Definition: worker.h:327
void quitAndWait() noexcept final
Calls quit() and blocks until the thread is finished.
Definition: worker.cpp:203
void start(QThread::Priority priority=QThread::InheritPriority)
Starts a thread and moves the worker into it.
Definition: worker.cpp:165
CContinuousWorker(QObject *owner, const QString &name)
Constructor.
Definition: worker.cpp:159
static const QString & worker()
Background task.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Just a subclass of QThread whose destructor waits for the thread to finish.
Definition: worker.h:38
~CRegularThread()
Destructor.
Definition: worker.cpp:34
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
CWorkerBase()
Constructor.
Definition: worker.cpp:124
void aboutToStart()
Emitted when the task is about to start.
void then(T *context, F functor)
Connects to a functor or method which will be called when the task is finished.
Definition: worker.h:97
void abandon() noexcept
Notify the task that its result is no longer needed, so it can finish early.
Definition: worker.cpp:141
static const QStringList & getLogCategories()
Log categories.
Definition: worker.cpp:128
void abandonAndWait() noexcept
Convenience to call abandon() followed by waitForFinished().
Definition: worker.cpp:147
bool isAbandoned() const
For the task to check whether it can finish early.
Definition: worker.cpp:153
bool hasStarted() const
True if the worker has started.
Definition: worker.h:182
~CWorkerBase()
Destructor.
Definition: worker.cpp:126
void setFinished()
Mark the task as finished.
Definition: worker.h:188
void waitForFinished() noexcept
Blocks until the task is finished.
Definition: worker.cpp:134
Class for doing some arbitrary parcel of work in its own thread.
Definition: worker.h:212
Free functions in swift::misc.
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26