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  m_updateTimer.setObjectName(m_name + ":timer");
164  }
165 
166  void CContinuousWorker::start(QThread::Priority priority)
167  {
168  SWIFT_VERIFY_X(!hasStarted(), Q_FUNC_INFO, "Tried to start a worker that was already started");
169  if (hasStarted()) { return; }
170 
171  // avoid message "QObject: Cannot create children for a parent that is in a different thread"
172  Q_ASSERT_X(CThreadUtils::isInThisThread(m_owner), Q_FUNC_INFO, "Needs to be started in owner thread");
173  emit this->aboutToStart();
174  setStarted();
175  auto *thread = new CRegularThread(m_owner);
176 
177  Q_ASSERT(m_owner); // must not be null
178  if (m_owner)
179  {
180  const QString ownerName =
181  m_owner->objectName().isEmpty() ? m_owner->metaObject()->className() : m_owner->objectName();
182  thread->setObjectName(ownerName + ": " + m_name);
183  }
184 
185  moveToThread(thread);
186  connect(thread, &QThread::started, this, &CContinuousWorker::initialize);
187  connect(thread, &QThread::finished, &m_updateTimer, &QTimer::stop);
188  connect(thread, &QThread::finished, this, &CContinuousWorker::cleanup);
189  connect(thread, &QThread::finished, this, &CContinuousWorker::finish);
190  thread->start(priority);
191  }
192 
193  void CContinuousWorker::quit() noexcept
194  {
195  this->setEnabled(false);
196 
197  // already in owner's thread? then return
198  if (this->thread() == m_owner->thread()) { return; }
199 
200  // remark: cannot stop timer here, as I am normally not in the correct thread
201  this->beforeQuit();
202  thread()->quit();
203  }
204 
206  {
207  this->setEnabled(false);
208 
209  // already in owner's thread? then return
210  if (this->thread() == m_owner->thread()) { return; }
211 
212  // called by own thread, will deadlock, return
213  if (CThreadUtils::isInThisThread(this)) { return; }
214 
215  QThread *workerThread = thread(); // must be before quit()
216  this->quit();
217 
218  // T647, discussed here:
219  // https://discordapp.com/channels/539048679160676382/539925070550794240/573260844004016148
220  const unsigned long waitTimeoutMs = this->waitTimeoutMs();
221  const QString name(this->getName());
222  qint64 waitTime = QDateTime::currentMSecsSinceEpoch();
223  const bool ok =
224  workerThread->wait(waitTimeoutMs);
226  waitTime = QDateTime::currentMSecsSinceEpoch() - waitTime;
227  const QString msg = QStringLiteral("Waiting for quitAndWait of '%1' for %2ms").arg(name).arg(waitTime);
228  const QByteArray msgBA = msg.toLatin1();
229  SWIFT_AUDIT_X(ok, Q_FUNC_INFO,
230  msgBA); // MS 2019-05 AUDIT because we want a stack trace of all threads, via breakpad
231  CLogMessage(this).info(msg);
232  Q_UNUSED(ok)
233  }
234 
235  void CContinuousWorker::startUpdating(int updateTimeSecs)
236  {
237  Q_ASSERT_X(this->hasStarted(), Q_FUNC_INFO, "Worker not yet started");
238  if (!CThreadUtils::isInThisThread(this))
239  {
240  // shift in correct thread
241  QPointer<CContinuousWorker> myself(this);
242  QTimer::singleShot(0, this, [=] {
243  if (!myself) { return; }
244  this->doIfNotFinished([=] { startUpdating(updateTimeSecs); });
245  });
246  return;
247  }
248 
249  // here in correct timer thread
250  if (updateTimeSecs < 0)
251  {
252  this->setEnabled(false);
253  m_updateTimer.stop();
254  }
255  else
256  {
257  this->setEnabled(true);
258  m_updateTimer.start(1000 * updateTimeSecs);
259  }
260  }
261 
263  {
264  if (!m_updateTimer.isActive()) { return; }
265 
266  // avoid "Timers cannot be stopped from another thread"
268  else
269  {
270  QPointer<CContinuousWorker> myself(this);
272  // stop timer in timer thread
273  if (!myself) { return; }
274  m_updateTimer.stop();
275  });
276  }
277  }
278 
279  void CContinuousWorker::finish()
280  {
281  this->setFinished();
282 
283  QThread *workerThread = this->thread();
284  Q_ASSERT_X(m_owner->thread()->isRunning(), Q_FUNC_INFO, "Owner thread's event loop already ended");
285 
286  // MS 2018-09 Now we post the DeferredDelete event from within the worker thread, but rely on it being
287  // dispatched
288  // by the owner thread. Posted events are moved along with the object when moveToThread is called.
289  this->deleteLater();
290 
291  this->moveToThread(
292  m_owner->thread()); // move worker back to the thread which constructed it, so there is no race on deletion
293  // must not access the worker beyond this point, as it now lives in the owner's thread and could be deleted at
294  // any moment
295 
296  QMetaObject::invokeMethod(workerThread, [workerThread] {
297  // quit and wait is redundant as the CRegularThread dtor will do that anyway, but put here for debugging
298  workerThread->quit();
299  const bool ok = workerThread->wait(5000);
300  const QString as = QStringLiteral("Worker thread '%2' refuses to stop after worker finished")
301  .arg(workerThread->objectName());
302  const QByteArray asBA = as.toLatin1();
303  SWIFT_AUDIT_X(ok, Q_FUNC_INFO, asBA);
304 
305  workerThread->deleteLater();
306  });
307  }
308 } // namespace swift::misc
virtual void beforeQuit() noexcept
Called before quit is called.
Definition: worker.h:325
void setEnabled(bool enabled)
Enabled (running)?
Definition: worker.h:304
virtual void cleanup()
Called when the thread is finished.
Definition: worker.h:321
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:193
virtual void initialize()
Called when the thread is started.
Definition: worker.h:318
virtual unsigned long waitTimeoutMs() const
Wait time for quitAndWait, 0 means not waiting.
Definition: worker.h:328
const QString & getName()
Name of the worker.
Definition: worker.h:311
void quitAndWait() noexcept final
Calls quit() and blocks until the thread is finished.
Definition: worker.cpp:205
QTimer m_updateTimer
timer which can be used by implementing classes
Definition: worker.h:333
void stopUpdateTimer()
Safely stop update time.
Definition: worker.cpp:262
void start(QThread::Priority priority=QThread::InheritPriority)
Starts a thread and moves the worker into it.
Definition: worker.cpp:166
CContinuousWorker(QObject *owner, const QString &name)
Constructor.
Definition: worker.cpp:159
void startUpdating(int updateTimeSecs)
Start updating (start/stop timer)
Definition: worker.cpp:235
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:70
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
void doIfNotFinished(F functor) const
Executes some code (in the caller's thread) if the task has not finished.
Definition: worker.h:108
~CWorkerBase()
Destructor.
Definition: worker.cpp:126
void setFinished()
Mark the task as finished.
Definition: worker.h:164
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:188
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
#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