1 #pragma once
2 
3 #include <atomic>
4 #include <condition_variable>
5 #include <mutex>
6 
7 #include <QThread>
8 
9 #include "util/logger.h"
10 
11 
12 /// A worker thread without an event loop.
13 ///
14 /// This object lives in the creating thread of the host, i.e. does not
15 /// run its own event loop. It does not use slots for communication
16 /// with its host which would otherwise still be executed in the host's
17 /// thread.
18 ///
19 /// Signals emitted from the internal worker thread by derived classes
20 /// will queued connections. Communication in the opposite direction is
21 /// accomplished by using lock-free types to avoid locking the host
22 /// thread through priority inversion. Lock-free types might also used
23 /// for any shared state that is read from the host thread after being
24 /// notified about changes.
25 ///
26 /// Derived classes or their owners are responsible to start the thread
27 /// with the desired priority.
28 class WorkerThread : public QThread {
29     Q_OBJECT
30 
31   public:
32     explicit WorkerThread(
33             const QString& name = QString(),
34             const QThread::Priority priority = QThread::InheritPriority);
35     /// The destructor must be triggered by calling deleteLater() to
36     /// ensure that the thread has already finished and is not running
37     /// while destroyed! Connect finished() to deleteAfter() and then
38     /// call stop() on the running worker thread explicitly to trigger
39     /// the destruction. Use deleteAfterFinished() for this purpose.
40     ~WorkerThread() override;
41 
42     void deleteAfterFinished();
43 
name()44     const QString& name() const {
45         return m_name;
46     }
47 
48     /// Commands the thread to suspend itself asap.
49     ///
50     /// Must not be invoked from the worker thread itself to
51     /// avoid race conditions!
52     void suspend();
53 
54     /// Resumes a suspended thread by waking it up.
55     ///
56     /// Must not be invoked from the worker thread itself to
57     /// avoid race conditions!
58     void resume();
59 
60     /// Wakes up a sleeping thread. If the thread has been suspended
61     /// it will fall asleep again. A suspended thread needs to be
62     /// resumed.
63     ///
64     /// Must not be invoked from the worker thread itself to
65     /// avoid race conditions!
66     void wake();
67 
68     /// Commands the thread to stop asap. This action is irreversible,
69     /// i.e. the thread cannot be restarted once it has been stopped.
70     ///
71     /// Must not be invoked from the worker thread itself to
72     /// avoid race conditions!
73     void stop();
74 
75     /// Non-blocking atomic read of the stop flag which indicates that
76     /// the thread is stopping, i.e. it will soon exit or already has
77     /// exited the run loop.
isStopping()78     bool isStopping() const {
79         return m_stop.load();
80     }
81 
82   protected:
83     void run() final;
84 
85     /// The internal run loop. Not to be confused with the Qt event
86     /// loop since the worker thread doesn't have one!
87     /// An implementation may exit this loop after all work is done,
88     /// which in turn exits and terminates the thread. The loop should
89     /// also be left asap when isStopping() returns true. This condition
90     /// should be checked repeatedly during execution of the loop and
91     /// especially before starting any expensive subtasks.
92     virtual void doRun() = 0;
93 
94     enum class TryFetchWorkItemsResult {
95         Ready,
96         Idle,
97     };
98 
99     /// Non-blocking function that determines whether the worker thread
100     /// is idle (i.e. no new tasks have been scheduled) and should be
101     /// either suspended until resumed or put to sleep until woken up.
102     ///
103     /// Implementing classes are able to control what to do if no more
104     /// work is currently available. Returning TryFetchWorkItemsResult::Idle
105     /// preserves the current suspend state and lets the thread sleep
106     /// until wake() is called.
107     ///
108     /// Implementing classes are responsible for storing the fetched
109     /// work items internally for later processing during
110     /// doRun().
111     ///
112     /// The stop flag does not have to be checked when entering this function,
113     /// because it has already been checked just before the invocation. Though
114     /// the fetch operation may check again before starting any expensive
115     /// internal subtask.
116     virtual TryFetchWorkItemsResult tryFetchWorkItems() = 0;
117 
118     /// Blocks while idle and not stopped. Returns true when new work items
119     /// for processing have been fetched and false if the thread has been
120     /// stopped while waiting.
121     bool awaitWorkItemsFetched();
122 
123     /// Blocks the worker thread while the suspend flag is set.
124     ///
125     /// Worker threads should invoke this yield-like method periodically.
126     /// Especially before starting any computational intensive work that
127     /// is expected to take some time.
128     ///
129     /// After returning from this function worker threads should also
130     /// check isStopping() and exit if they have been asked to stop.
131     ///
132     /// This function must not be called from tryFetchWorkItems()
133     /// to avoid a deadlock on the non-recursive mutex!
134     void sleepWhileSuspended();
135 
136   private:
137     const QString m_name;
138     const QThread::Priority m_priority;
139 
140     const mixxx::Logger m_logger;
141 
142     std::atomic<bool> m_suspend;
143     std::atomic<bool> m_stop;
144 
145     std::mutex m_sleepMutex;
146     std::condition_variable m_sleepWaitCond;
147 };
148