OLD | NEW |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
3 * Copyright (C) 2006-2016 Eyeo GmbH | 3 * Copyright (C) 2006-2016 Eyeo GmbH |
4 * | 4 * |
5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
8 * | 8 * |
9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
13 * | 13 * |
14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
16 */ | 16 */ |
17 | 17 |
18 #if !defined(ADBLOCKPLUS_SCHEDULER_H) | 18 #if !defined(ADBLOCKPLUS_SCHEDULER_H) |
19 #define ADBLOCKPLUS_SCHEDULER_H | 19 #define ADBLOCKPLUS_SCHEDULER_H |
20 | 20 |
21 #include <condition_variable> | 21 #include <condition_variable> |
22 #include <list> | 22 #include <list> |
23 #include <memory> | 23 #include <memory> |
24 #include <mutex> | 24 #include <mutex> |
25 #include <functional> | 25 #include <functional> |
26 #include <queue> | 26 #include <queue> |
27 #include <thread> | 27 #include <thread> |
28 | 28 |
29 namespace AdblockPlus | |
30 { | |
31 /* | |
32 * Adapter class for heap allocated function objects | |
33 * | |
34 * The legacy behavior of the original threading regime combined task life | |
35 * cycle and execution. Previously, tasks were allocated by the user and | |
36 * deleted at the end of execution internally. This adapter class allows | |
37 * separation of these concerns. It allows the legacy allocation behavior | |
38 * without burdening the new scheduler with any concern over task life cycle. | |
39 */ | |
40 template<class T> | |
41 class HeapFunction | |
42 { | |
43 std::shared_ptr<T> body; | |
44 | |
45 public: | |
46 HeapFunction(std::shared_ptr<T> body) | |
47 :body(body) | |
48 {} | |
49 | |
50 void operator()() | |
51 { | |
52 if (body) | |
53 { | |
54 body->operator()(); | |
55 } | |
56 } | |
57 }; | |
58 | |
59 /* | |
60 * Utility function uses type inference to simplify expressions at point of us
e. | |
61 */ | |
62 template<class T> | |
63 HeapFunction<T> MakeHeapFunction(std::shared_ptr<T> body) | |
64 { | |
65 return HeapFunction<T>(body); | |
66 } | |
67 } | |
68 | |
69 /** | 29 /** |
70 * Interface class for scheduled tasks. | 30 * Interface class for scheduled tasks. |
71 */ | 31 */ |
72 struct TaskFunctionInterface | 32 struct TaskFunctionInterface |
73 { | 33 { |
74 /** | 34 /** |
75 * The main function of the task, | 35 * The main function of the task, |
76 * the moral equivalent to the main function of the thread. | 36 * the moral equivalent to the main function of the thread. |
77 */ | 37 */ |
78 virtual void operator()() = 0; | 38 virtual void operator()() = 0; |
79 /** | 39 /** |
80 * Request that the task end itself early, without needing to complete. | 40 * Request that the task end itself early, without needing to complete. |
81 * Reserved for future use. | 41 * Reserved for future use. |
82 */ | 42 */ |
83 virtual void Interrupt() {}; | 43 virtual void Interrupt() {}; |
84 }; | 44 }; |
85 | 45 |
86 /** | 46 /** |
87 * Execute a task immediately in detached thread that's used only for this task. | |
88 * | |
89 * The present version is nothing more than a rewrite of the legacy behavior, | |
90 * which was to create a new thread for each task. | |
91 */ | |
92 void StartImmediatelyInSingleUseDetachedThread(std::function<void()> task); | |
93 | |
94 /** | |
95 * Active object to run arbitrary function objects. | 47 * Active object to run arbitrary function objects. |
96 * | 48 * |
97 * Operations execute in a thread owned by this instance. | 49 * Operations execute in a thread owned by this instance. |
98 * If a user wants to execute an operation in a different thread, | 50 * If a user wants to execute an operation in a different thread, |
99 * they have the responsibility to set up whatever additional | 51 * they have the responsibility to set up whatever additional |
100 * facilities may be required. | 52 * facilities may be required. |
101 */ | 53 */ |
102 class OperationRunner | 54 class OperationRunner |
103 { | 55 { |
104 /** | 56 /** |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
189 * | 141 * |
190 * The present version has some limitations and inefficiencies, | 142 * The present version has some limitations and inefficiencies, |
191 * trading them off for ease of incremental implementation. | 143 * trading them off for ease of incremental implementation. |
192 * - Separate schedulers for each `JsEngine` without any shared facilities. | 144 * - Separate schedulers for each `JsEngine` without any shared facilities. |
193 * - Working threads are single-use. No provision for a thread pool. | 145 * - Working threads are single-use. No provision for a thread pool. |
194 * - Tasks are simple function objects. Its possible to hasten termination | 146 * - Tasks are simple function objects. Its possible to hasten termination |
195 * in v8 by calling `TerminateExecution()`, for example, | 147 * in v8 by calling `TerminateExecution()`, for example, |
196 * but there's no interface for this. | 148 * but there's no interface for this. |
197 * - No support for delayed execution to support JavaScript `SetTimeout`. | 149 * - No support for delayed execution to support JavaScript `SetTimeout`. |
198 */ | 150 */ |
199 template<class Worker> | 151 template<class Worker, class UserData> |
200 class SchedulerT | 152 class SchedulerT |
201 { | 153 { |
202 /** | 154 /** |
| 155 * A task as scheduled for execution. |
| 156 * |
| 157 * This class is a combination of two kinds of data: |
| 158 * 1. An external object containing the body of the task. |
| 159 * 2. All the internal elements required to run the task. |
| 160 */ |
| 161 template<class UserData> |
| 162 struct ScheduledTask |
| 163 { |
| 164 /** |
| 165 * The external task. |
| 166 * This member comes to us from our Run() method. |
| 167 */ |
| 168 std::shared_ptr<TaskFunctionInterface> body; |
| 169 /** |
| 170 * The only internal data at present is the worker thread. |
| 171 */ |
| 172 Worker worker; |
| 173 /** |
| 174 * Data associated with this task by the user of the scheduler |
| 175 */ |
| 176 std::shared_ptr<UserData> userData; |
| 177 /** |
| 178 * Member worker is default-constructed |
| 179 * because VS2012 insists on copy constructors when it doesn't need them. |
| 180 */ |
| 181 ScheduledTask(std::shared_ptr<TaskFunctionInterface>&& body, std::shared_ptr
<UserData>&& ud) |
| 182 : body(std::forward<std::shared_ptr<TaskFunctionInterface>>(body)), |
| 183 userData(std::forward<std::shared_ptr<UserData>>(ud)) |
| 184 {} |
| 185 }; |
| 186 |
| 187 /** |
203 * Shared mutex for all synchronization | 188 * Shared mutex for all synchronization |
204 */ | 189 */ |
205 std::mutex m; | 190 std::mutex m; |
206 typedef std::unique_lock<std::mutex> UniqueLockType; | 191 typedef std::unique_lock<std::mutex> UniqueLockType; |
207 | 192 |
| 193 typedef std::list<ScheduledTask<UserData>> TaskListType; |
208 /** | 194 /** |
209 * A list containing an entry for each running task. | 195 * A list containing an entry for each running task. |
210 */ | 196 */ |
211 std::list<Worker> taskList; | 197 TaskListType taskList; |
212 | 198 |
213 /** | 199 /** |
214 * Condition variable notified when a running task is removed from its list | 200 * Condition variable notified when a running task is removed from its list |
215 */ | 201 */ |
216 std::condition_variable taskRemoveCv; | 202 std::condition_variable taskRemoveCv; |
217 | 203 |
218 /** | 204 /** |
219 * Maintains a separate thread that is able to join completed worker threads. | 205 * Maintains a separate thread that is able to join completed worker threads. |
220 */ | 206 */ |
221 OperationRunner monitor; | 207 OperationRunner monitor; |
222 | 208 |
223 /** | 209 /** |
| 210 * State flag. |
| 211 * |
| 212 * We only have two states: normal and terminating. |
| 213 */ |
| 214 bool isNormal; |
| 215 |
| 216 /** |
224 * Execution wrapper for a task. | 217 * Execution wrapper for a task. |
225 * @param task | 218 * @param task |
226 * An iterator to the task within the task list. | 219 * An iterator to the task within the task list. |
227 */ | 220 */ |
228 void WorkerMain(std::function<void()> body, typename std::list<Worker>::iterat
or task) | 221 void WorkerMain(typename TaskListType::iterator task) |
229 { | 222 { |
230 body(); | 223 (*task->body)(); |
231 monitor.Run([this, task]() { JoinAndRemove(task); }); | 224 monitor.Run([this, task]() { JoinAndRemove(task); }); |
232 } | 225 } |
233 | 226 |
234 /** | 227 /** |
235 * Joins a task and then removes it from the task list. | 228 * Joins a task and then removes it from the task list. |
236 * @param task | 229 * @param task |
237 * An iterator to the task within the task list. | 230 * An iterator to the task within the task list. |
238 * | 231 * |
239 * This is the only function that may remove tasks from the running task list. | 232 * This is the only function that may remove tasks from the running task list. |
240 */ | 233 */ |
241 void JoinAndRemove(typename std::list<Worker>::iterator task) | 234 void JoinAndRemove(typename TaskListType::iterator task) |
242 { | 235 { |
243 task->Join(); | 236 task->worker.Join(); |
244 { | 237 { |
245 UniqueLockType ul(m); | 238 UniqueLockType ul(m); |
246 taskList.erase(task); | 239 taskList.erase(task); |
247 } | 240 } |
248 taskRemoveCv.notify_all(); | 241 taskRemoveCv.notify_all(); |
249 } | 242 } |
250 | 243 |
251 public: | 244 public: |
252 SchedulerT() | 245 SchedulerT() |
| 246 : isNormal(true) |
253 {} | 247 {} |
254 | 248 |
255 ~SchedulerT() | 249 ~SchedulerT() |
256 { | 250 { |
257 // `JoinAll()` may be called prior to destruction but, however, | 251 // `JoinAll()` may be called prior to destruction but, however, |
258 // there is no requirement that it be called beforehand. | 252 // there is no requirement that it be called beforehand. |
259 // We call it in the destructor to ensure all tasks have completed. | 253 // We call it in the destructor to ensure all tasks have completed. |
260 // If we do not, we risk destruction of a joinable thread, | 254 // If we do not, we risk destruction of a joinable thread, |
261 // which would generate a call to `std::terminate()`. | 255 // which would generate a call to `std::terminate()`. |
| 256 ShutDown(); |
262 JoinAll(); | 257 JoinAll(); |
263 } | 258 } |
264 | 259 |
265 /** | 260 /** |
| 261 * Interrupt all pending tasks so that they'll terminate soon. |
| 262 * Execution may proceed after a call to Interrupt(), |
| 263 * but it does indicate that a task should terminate as soon as it can. |
| 264 */ |
| 265 void ShutDown() |
| 266 { |
| 267 UniqueLockType ul(m); |
| 268 // Ensure the task list does not grow during shutdown. |
| 269 isNormal = false; |
| 270 |
| 271 for (auto& it : taskList) |
| 272 { |
| 273 it.body->Interrupt(); |
| 274 } |
| 275 } |
| 276 |
| 277 /** |
266 * Construct a `Worker` and run a task in it immediately. | 278 * Construct a `Worker` and run a task in it immediately. |
267 * | 279 * |
268 * This is the only function that may add tasks to the running task list. | 280 * This is the only function that may add tasks to the running task list. |
269 */ | 281 */ |
270 void Run(std::function<void()> body) | 282 void Run(std::shared_ptr<TaskFunctionInterface>&& body, std::shared_ptr<UserDa
ta>&& ud) |
271 { | 283 { |
272 UniqueLockType ul(m); | 284 UniqueLockType ul(m); |
| 285 if (!isNormal) |
| 286 { |
| 287 // We don't run new tasks if we're in a terminating state |
| 288 return; |
| 289 } |
273 // Note that the interface to `Worker` uses a constructor and not a factory | 290 // Note that the interface to `Worker` uses a constructor and not a factory |
274 // Here we construct in place, avoiding both move and copy. | 291 // Here we construct in place, avoiding both move and copy. |
275 auto tt = taskList.emplace(taskList.begin()); | 292 auto tt = taskList.emplace(taskList.begin(), |
276 tt->Run([this, body, tt]() { WorkerMain(body, tt); }); | 293 std::forward<std::shared_ptr<TaskFunctionInterface>>(body), |
| 294 std::forward<std::shared_ptr<UserData>>(ud)); |
| 295 tt->worker.Run([this, tt]() { WorkerMain(tt); }); |
277 } | 296 } |
278 | 297 |
279 /** | 298 /** |
280 * Block until all running threads have been joined and the task list is empty
. | 299 * Block until all running threads have been joined and the task list is empty
. |
281 * | 300 * |
282 * This class does not take on any responsibility to keep the task list | 301 * This class does not take on any responsibility to keep the task list |
283 * from growing while this function executes. | 302 * from growing while this function executes. |
284 * Improper use can result in this function blocking forever; don't do that. | 303 * Improper use can result in this function blocking forever; don't do that. |
285 */ | 304 */ |
286 void JoinAll() | 305 void JoinAll() |
287 { | 306 { |
288 // The call to `Worker::Join()` is in another function, | 307 // The call to `Worker::Join()` is in another function, |
289 // namely `JoinAndRemove()`, which also removes items from the task list. | 308 // namely `JoinAndRemove()`, which also removes items from the task list. |
290 // Thus all we do here is to wait for the task list to empty. | 309 // Thus all we do here is to wait for the task list to empty. |
291 UniqueLockType ul(m); | 310 UniqueLockType ul(m); |
292 taskRemoveCv.wait(ul, [this]() -> bool { return taskList.empty(); }); | 311 taskRemoveCv.wait(ul, [this]() -> bool { return taskList.empty(); }); |
293 } | 312 } |
294 }; | 313 }; |
295 | 314 |
296 #endif | 315 #endif |
OLD | NEW |