| 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 |