1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#ifndef ENGINE_SHARED_JOBS_H
4#define ENGINE_SHARED_JOBS_H
5
6#include <base/lock.h>
7#include <base/sphore.h>
8
9#include <atomic>
10#include <deque>
11#include <memory>
12#include <vector>
13
14/**
15 * Jobs and job pool.
16 *
17 * @defgroup Jobs Jobs
18 */
19
20/**
21 * A job which runs in a worker thread of a job pool.
22 *
23 * @ingroup Jobs
24 *
25 * @see CJobPool
26 */
27class IJob
28{
29 friend class CJobPool;
30
31public:
32 /**
33 * The state of a job in the job pool.
34 */
35 enum EJobState
36 {
37 /**
38 * Job has been created/queued but not started on a worker thread yet.
39 */
40 STATE_QUEUED = 0,
41
42 /**
43 * Job is currently running on a worker thread.
44 */
45 STATE_RUNNING,
46
47 /**
48 * Job was completed successfully.
49 */
50 STATE_DONE,
51
52 /**
53 * Job was aborted. Note the job may or may not still be running while
54 * in this state.
55 *
56 * @see IsAbortable
57 */
58 STATE_ABORTED,
59 };
60
61private:
62 std::shared_ptr<IJob> m_pNext;
63 std::atomic<EJobState> m_State;
64 std::atomic<bool> m_Abortable;
65
66protected:
67 /**
68 * Performs tasks in a worker thread.
69 */
70 virtual void Run() = 0;
71
72 /**
73 * Sets whether this job can be aborted.
74 *
75 * @remark Has no effect if the job has already been aborted.
76 *
77 * @see IsAbortable
78 */
79 void Abortable(bool Abortable);
80
81public:
82 IJob();
83 virtual ~IJob();
84
85 IJob(const IJob &Other) = delete;
86 IJob &operator=(const IJob &Other) = delete;
87
88 /**
89 * Returns the state of the job.
90 *
91 * @remark Accessing jobs in any other way that with the base functions of `IJob`
92 * is generally not thread-safe unless the job is in `STATE_DONE` or has not been
93 * enqueued yet.
94 *
95 * @return State of the job.
96 */
97 EJobState State() const;
98
99 /**
100 * Returns whether the job was completed or aborted, i.e. whether it's not still
101 * queued or running.
102 *
103 * @return `true` if the job is done, `false` otherwise.
104 *
105 * @remark This function also considers aborted jobs to be done, but aborted jobs
106 * may still be running until the cancelation is accepted. Ensure the job's state
107 * is `STATE_DONE` before accessing any results of the job.
108 */
109 bool Done() const;
110
111 /**
112 * Aborts the job, if it can be aborted.
113 *
114 * @return `true` if abort was accepted, `false` otherwise.
115 *
116 * @remark May be overridden to delegate abort to other jobs. Note that this
117 * function may be called from any thread and should be thread-safe.
118 */
119 virtual bool Abort();
120
121 /**
122 * Returns whether the job can be aborted. Jobs that are abortable may have
123 * their state set to `STATE_ABORTED` at any point if the job was aborted.
124 * The job state should be checked periodically in the `Run` function and the
125 * job should terminate at the earliest, safe opportunity when aborted.
126 * Scheduled jobs which are not abortable are guaranteed to fully complete
127 * before the job pool is shut down.
128 *
129 * @return `true` if the job can be aborted, `false` otherwise.
130 */
131 bool IsAbortable() const;
132};
133
134/**
135 * A job pool which runs jobs in one or more worker threads.
136 *
137 * @ingroup Jobs
138 *
139 * @see IJob
140 */
141class CJobPool
142{
143 std::vector<void *> m_vpThreads;
144 std::atomic<bool> m_Shutdown;
145
146 CLock m_Lock;
147 SEMAPHORE m_Semaphore;
148 std::shared_ptr<IJob> m_pFirstJob GUARDED_BY(m_Lock);
149 std::shared_ptr<IJob> m_pLastJob GUARDED_BY(m_Lock);
150
151 CLock m_LockRunning;
152 std::deque<std::shared_ptr<IJob>> m_RunningJobs GUARDED_BY(m_LockRunning);
153
154 static void WorkerThread(void *pUser) NO_THREAD_SAFETY_ANALYSIS;
155 void RunLoop() NO_THREAD_SAFETY_ANALYSIS;
156
157public:
158 CJobPool();
159 ~CJobPool();
160
161 /**
162 * Initializes the job pool with the given number of worker threads.
163 *
164 * @param NumThreads The number of worker threads.
165 *
166 * @remark Must be called on the main thread.
167 */
168 void Init(int NumThreads) REQUIRES(!m_Lock);
169
170 /**
171 * Shuts down the job pool. Aborts all abortable jobs. Then waits for all
172 * worker threads to complete all remaining queued jobs and terminate.
173 *
174 * @remark Must be called on the main thread.
175 */
176 void Shutdown() REQUIRES(!m_Lock) REQUIRES(!m_LockRunning);
177
178 /**
179 * Adds a job to the queue of the job pool.
180 *
181 * @param pJob The job to enqueue.
182 *
183 * @remark If the job pool is already shutting down, no additional jobs
184 * will be enqueue anymore. Abortable jobs will immediately be aborted.
185 */
186 void Add(std::shared_ptr<IJob> pJob) REQUIRES(!m_Lock);
187};
188#endif
189