| Index: src/Scheduler.h | 
| =================================================================== | 
| --- a/src/Scheduler.h | 
| +++ b/src/Scheduler.h | 
| @@ -18,8 +18,13 @@ | 
| #if !defined(ADBLOCKPLUS_SCHEDULER_H) | 
| #define ADBLOCKPLUS_SCHEDULER_H | 
| +#include <condition_variable> | 
| +#include <list> | 
| #include <memory> | 
| +#include <mutex> | 
| #include <functional> | 
| +#include <queue> | 
| +#include <thread> | 
| namespace AdblockPlus | 
| { | 
| @@ -59,20 +64,216 @@ | 
| { | 
| return HeapFunction<T>(body); | 
| } | 
| +} | 
| - /* | 
| - * Scheduler for the execution of tasks. | 
| - * | 
| - * The present version is nothing more than a rewrite of the legacy behavior, | 
| - * which was to create a new thread for each task. | 
| +/* | 
| + * Execute a task immediately in detached thread that's used only for this task. | 
| + * | 
| + * The present version is nothing more than a rewrite of the legacy behavior, | 
| + * which was to create a new thread for each task. | 
| + */ | 
| +void StartImmediatelyInSingleUseDetachedThread(std::function<void()> task); | 
| + | 
| +/** | 
| + * Active object to run arbitrary function objects. | 
| + * | 
| + * Operations execute in a thread owned by this instance. | 
| + * If a user wants to execute an operation in a different thread, | 
| 
 
sergei
2017/01/20 13:08:14
We don't need this part "If a user wants to execut
 
Eric
2017/03/30 17:16:59
Documenting responsibilities is part of what makes
 
sergei
2017/04/03 15:35:33
Sorry, but I have not seen on the provisioned link
 
 | 
| + * they have the responsibility to set up whatever additional | 
| + * facilities may be required. | 
| + */ | 
| +class OperationRunner | 
| 
 
sergei
2017/01/20 13:08:14
Why not to call it ActiveObject?
 
Eric
2017/03/30 17:16:59
Because it's not a generic active object. It's tai
 
sergei
2017/04/03 15:35:33
Maybe it's not a generic active object but it is a
 
 | 
| +{ | 
| + /** | 
| + * Shared mutex for all synchronization | 
| */ | 
| - class Scheduler | 
| + std::mutex m; | 
| + typedef std::unique_lock<std::mutex> UniqueLockType; | 
| + /** | 
| + * Condition variable notified when a operation is added to its queue | 
| + */ | 
| + std::condition_variable cv; | 
| + /** | 
| + * Operation queue for internal communication | 
| + */ | 
| + std::queue<std::function<void()>> queue; | 
| 
 
sergei
2017/01/20 13:08:14
Basically, it's a synchronized queue which has alr
 
Eric
2017/03/30 17:16:59
It's not a separate class because the mutex is use
 
sergei
2017/04/03 15:35:33
Could you please point to that race condition in t
 
 | 
| + /** | 
| + * The thread for the operation queue | 
| + */ | 
| + std::thread th; | 
| + /** | 
| + * Flag to keep the operation queue running | 
| + */ | 
| + bool isRunning; | 
| + /** | 
| + * Thread main for the operation queue thread. | 
| + */ | 
| + void ThreadMain(); | 
| + | 
| +public: | 
| + /** | 
| + * Execution thread starts during construction. | 
| + */ | 
| + OperationRunner(); | 
| + | 
| + /** | 
| + * Note: The execution thread must end before the destructor returns. | 
| + */ | 
| + ~OperationRunner(); | 
| + | 
| + /** | 
| + * Queue up an operation for execution in the queue's thread. | 
| + */ | 
| + void Run(std::function<void()> f); | 
| +}; | 
| + | 
| +/** | 
| + * A worker with a single-use-thread. | 
| + */ | 
| +class SingleUseWorker | 
| 
 
sergei
2017/01/20 13:08:13
It looks like there was a lot of discussions, fina
 
Eric
2017/03/30 17:16:59
It doesn't look from my perspective that there wer
 
sergei
2017/04/03 15:35:32
There were a lot of talks regarding designing a th
 
 | 
| +{ | 
| + /** | 
| + * The underlying thread | 
| + */ | 
| + std::thread th; | 
| + | 
| +public: | 
| + /** | 
| + * Constructor does not start thread; that happens in `Run()`. | 
| + */ | 
| + SingleUseWorker(); | 
| + ~SingleUseWorker(); | 
| + /** | 
| + * Run a task | 
| + * | 
| + * This class may rely on the restriction | 
| + * that the user may call this function at most once. | 
| + */ | 
| + void Run(std::function<void()> f); | 
| + /** | 
| + * Synchronize with point of completion of task | 
| + * | 
| + * The present class calls `join()` on the thread, | 
| + * but alternates might synchronize in other ways. | 
| + */ | 
| + void Join(); | 
| +}; | 
| + | 
| +/** | 
| + * A simple scheduler. | 
| + * | 
| + * The scheduler keeps track of executing threads | 
| + * instead of detaching them and keeping no record of what's executing. | 
| + * The main goal at this stage is to be able to join all running threads. | 
| + * Since you can't call `join()` on a thread from within that thread, | 
| + * this class contains its a separate thread to be able to make such calls. | 
| + * This thread runs a generic operation queue | 
| + * that does more than simply join zombie threads. | 
| + * | 
| 
 
sergei
2017/03/30 13:14:41
Could you please move that Scheduler with the func
 
Eric
2017/03/30 14:20:55
You might know what you want, but this description
 
sergei
2017/04/03 15:35:33
So, what are the difficulties to create a coderevi
 
 | 
| + * The present version has some limitations and inefficiencies, | 
| + * trading them off for ease of incremental implementation. | 
| + * - Separate schedulers for each `JsEngine` without any shared facilities. | 
| 
 
sergei
2017/01/20 13:08:14
I would remove this string because whether there i
 
Eric
2017/03/30 17:16:58
Since you've not delved into this code the way I h
 
sergei
2017/04/03 15:35:33
I think that scheduler should not be so direct mem
 
 | 
| + * - Working threads are single-use. No provision for a thread pool. | 
| 
 
sergei
2017/01/20 13:08:13
That's also questionable.
 
Eric
2017/03/30 17:16:59
It is absolutely not questionable that this class,
 
sergei
2017/04/03 15:35:33
Why should it have a thread pool? It's similar to
 
 | 
| + * - Tasks are simple function objects. Its possible to hasten termination | 
| + * in v8 by calling `TerminateExecution()`, for example, | 
| + * but there's no interface for this. | 
| + * - No support for delayed execution to support JavaScript `SetTimeout`. | 
| + */ | 
| +template<class Worker> | 
| +class SchedulerT | 
| +{ | 
| + /** | 
| + * Shared mutex for all synchronization | 
| + */ | 
| + std::mutex m; | 
| 
 
sergei
2017/01/20 13:08:14
it should be rather std::recursive_mutex because i
 
Eric
2017/03/30 17:16:58
No, it should not be recursive mutex. That would b
 
sergei
2017/04/03 15:35:33
Acknowledged.
 
 | 
| + typedef std::unique_lock<std::mutex> UniqueLockType; | 
| + | 
| + /** | 
| + * A list containing an entry for each running task. | 
| + */ | 
| + std::list<Worker> taskList; | 
| + | 
| + /** | 
| + * Condition variable notified when a running task is removed from its list | 
| + */ | 
| + std::condition_variable taskRemoveCv; | 
| + | 
| + /** | 
| + * Maintains a separate thread that is able to join completed worker threads. | 
| + */ | 
| + OperationRunner monitor; | 
| + | 
| + /** | 
| + * Execution wrapper for a task. | 
| + * @param task | 
| + * An iterator to the task within the task list. | 
| + */ | 
| + void WorkerMain(std::function<void()> body, typename std::list<Worker>::iterator task) | 
| { | 
| - public: | 
| - /* | 
| - * Execute a task immediately in a thread created for this task only | 
| - */ | 
| - static void StartImmediatelyInSingleUseThread(std::function<void()> task); | 
| - }; | 
| -} | 
| + body(); | 
| + monitor.Run([this, task]() { JoinAndRemove(task); }); | 
| + } | 
| + | 
| + /** | 
| + * Joins a task and then removes it from the task list. | 
| + * @param task | 
| + * An iterator to the task within the task list. | 
| + * | 
| + * This is the only function that may remove tasks from the running task list. | 
| + */ | 
| + void JoinAndRemove(typename std::list<Worker>::iterator task) | 
| + { | 
| + task->Join(); | 
| 
 
sergei
2017/04/03 15:35:33
here is a race condition, it can happen that move-
 
 | 
| + { | 
| + UniqueLockType ul(m); | 
| + taskList.erase(task); | 
| + } | 
| + taskRemoveCv.notify_all(); | 
| + } | 
| + | 
| +public: | 
| + SchedulerT() | 
| + {} | 
| + | 
| + ~SchedulerT() | 
| + { | 
| + // `JoinAll()` may be called prior to destruction but, however, | 
| + // there is no requirement that it be called beforehand. | 
| + // We call it in the destructor to ensure all tasks have completed. | 
| + // If we do not, we risk destruction of a joinable thread, | 
| + // which would generate a call to `std::terminate()`. | 
| + JoinAll(); | 
| + } | 
| + | 
| + /** | 
| + * Construct a `Worker` and run a task in it immediately. | 
| + * | 
| + * This is the only function that may add tasks to the running task list. | 
| + */ | 
| + void Run(std::function<void()> body) | 
| 
 
sergei
2017/01/20 13:08:13
It would be easier to read if argument "body" is r
 
Eric
2017/03/30 17:16:58
I've used "body" consistently for a function that'
 
sergei
2017/04/03 15:35:33
I still think that another name is better here, bo
 
 | 
| + { | 
| + UniqueLockType ul(m); | 
| + // Note that the interface to `Worker` uses a constructor and not a factory | 
| + // Here we construct in place, avoiding both move and copy. | 
| + auto tt = taskList.emplace(taskList.begin()); | 
| + tt->Run([this, body, tt]() { WorkerMain(body, tt); }); | 
| 
 
sergei
2017/01/20 13:08:13
I'm not sure that it's a wise decision to assume t
 
Eric
2017/03/30 17:16:58
This is not just an assumption. It's a design prin
 
sergei
2017/04/03 15:35:32
OK, let's say I want to implement timers using Set
 
 | 
| + } | 
| + | 
| + /** | 
| + * Block until all running threads have been joined and the task list is empty. | 
| + * | 
| + * This class does not take on any responsibility to keep the task list | 
| + * from growing while this function executes. | 
| + * Improper use can result in this function blocking forever; don't do that. | 
| + */ | 
| + void JoinAll() | 
| + { | 
| + // The call to `Worker::Join()` is in another function, | 
| + // namely `JoinAndRemove()`, which also removes items from the task list. | 
| + // Thus all we do here is to wait for the task list to empty. | 
| + UniqueLockType ul(m); | 
| + taskRemoveCv.wait(ul, [this]() -> bool { return taskList.empty(); }); | 
| + } | 
| +}; | 
| + | 
| #endif |