1 module hunt.concurrency.TaskPool;
2 
3 import hunt.concurrency.SimpleQueue;
4 import hunt.logging.ConsoleLogger;
5 import hunt.system.Memory;
6 import hunt.util.Common;
7 
8 import core.thread;
9 import core.atomic;
10 import core.sync.condition;
11 import core.sync.mutex;
12 
13 import std.traits;
14 
15 private enum TaskStatus : ubyte {
16     ready,
17     processing,
18     done
19 }
20 
21 /* Atomics code.  These forward to core.atomic, but are written like this
22    for two reasons:
23 
24    1.  They used to actually contain ASM code and I don' want to have to change
25        to directly calling core.atomic in a zillion different places.
26 
27    2.  core.atomic has some misc. issues that make my use cases difficult
28        without wrapping it.  If I didn't wrap it, casts would be required
29        basically everywhere.
30 */
31 private void atomicSetUbyte(T)(ref T stuff, T newVal)
32         if (__traits(isIntegral, T) && is(T : ubyte)) {
33     //core.atomic.cas(cast(shared) &stuff, stuff, newVal);
34     atomicStore(*(cast(shared)&stuff), newVal);
35 }
36 
37 private ubyte atomicReadUbyte(T)(ref T val)
38         if (__traits(isIntegral, T) && is(T : ubyte)) {
39     return atomicLoad(*(cast(shared)&val));
40 }
41 
42 // This gets rid of the need for a lot of annoying casts in other parts of the
43 // code, when enums are involved.
44 private bool atomicCasUbyte(T)(ref T stuff, T testVal, T newVal)
45         if (__traits(isIntegral, T) && is(T : ubyte)) {
46     return core.atomic.cas(cast(shared)&stuff, testVal, newVal);
47 }
48 
49 
50 /**
51  * 
52  */
53 class AbstractTask : Runnable {
54 
55     Throwable exception;
56     ubyte taskStatus = TaskStatus.ready;
57 
58     final void run() {
59         atomicSetUbyte(taskStatus, TaskStatus.processing);
60         try {
61             onRun();
62         } catch (Throwable e) {
63             exception = e;
64             debug warning(e.msg);
65         }
66 
67         atomicSetUbyte(taskStatus, TaskStatus.done);
68     }
69 
70     abstract protected void onRun();
71 
72     bool done() @property {
73         if (atomicReadUbyte(taskStatus) == TaskStatus.done) {
74             if (exception) {
75                 throw exception;
76             }
77             return true;
78         }
79         return false;
80     }
81 }
82 
83 /**
84 */
85 class Task(alias fun, Args...) : AbstractTask {
86     Args _args;
87 
88     static if (Args.length > 0) {
89         this(Args args) {
90             _args = args;
91         }
92     } else {
93         this() {
94         }
95     }
96 
97     /**
98     The return type of the function called by this `Task`.  This can be
99     `void`.
100     */
101     alias ReturnType = typeof(fun(_args));
102 
103     static if (!is(ReturnType == void)) {
104         static if (is(typeof(&fun(_args)))) {
105             // Ref return.
106             ReturnType* returnVal;
107 
108             ref ReturnType fixRef(ReturnType* val) {
109                 return *val;
110             }
111 
112         } else {
113             ReturnType returnVal;
114 
115             ref ReturnType fixRef(ref ReturnType val) {
116                 return val;
117             }
118         }
119     }
120 
121     private static void impl(AbstractTask myTask) {
122         auto myCastedTask = cast(typeof(this)) myTask;
123         static if (is(ReturnType == void)) {
124             fun(myCastedTask._args);
125         } else static if (is(typeof(addressOf(fun(myCastedTask._args))))) {
126             myCastedTask.returnVal = addressOf(fun(myCastedTask._args));
127         } else {
128             myCastedTask.returnVal = fun(myCastedTask._args);
129         }
130     }
131 
132     protected override void onRun() {
133         impl(this);
134     }
135 }
136 
137 T* addressOf(T)(ref T val) {
138     return &val;
139 }
140 
141 auto makeTask(alias fun, Args...)(Args args) {
142     return new Task!(fun, Args)(args);
143 }
144 
145 auto makeTask(F, Args...)(F delegateOrFp, Args args)
146         if (is(typeof(delegateOrFp(args)))) // && !isSafeTask!F
147         {
148     return new Task!(run, F, Args)(delegateOrFp, args);
149 }
150 
151 // Calls `fpOrDelegate` with `args`.  This is an
152 // adapter that makes `Task` work with delegates, function pointers and
153 // functors instead of just aliases.
154 ReturnType!F run(F, Args...)(F fpOrDelegate, ref Args args) {
155     return fpOrDelegate(args);
156 }
157 
158 /*
159 This class serves two purposes:
160 
161 1.  It distinguishes std.parallelism threads from other threads so that
162     the std.parallelism daemon threads can be terminated.
163 
164 2.  It adds a reference to the pool that the thread is a member of,
165     which is also necessary to allow the daemon threads to be properly
166     terminated.
167 */
168 final class ParallelismThread : Thread {
169     this(void delegate() dg) {
170         super(dg);
171         taskQueue = new NonBlockingQueue!(AbstractTask)();
172     }
173 
174     TaskPool pool;
175     NonBlockingQueue!(AbstractTask) taskQueue;
176 }
177 
178 /**
179 */
180 enum PoolState : ubyte {
181     running,
182     finishing,
183     stopNow
184 }
185 
186 /**
187 */
188 class TaskPool {
189 
190     private ParallelismThread[] pool;
191     private PoolState status = PoolState.running;
192 
193     // The instanceStartIndex of the next instance that will be created.
194     // __gshared size_t nextInstanceIndex = 1;
195 
196     // The index of the first thread in this instance.
197     // immutable size_t instanceStartIndex;
198 
199     // The index of the current thread.
200     static size_t threadIndex;
201 
202     // The index that the next thread to be initialized in this pool will have.
203     shared size_t nextThreadIndex;
204 
205     Condition workerCondition;
206     Condition waiterCondition;
207     Mutex queueMutex;
208     Mutex waiterMutex; // For waiterCondition
209 
210     bool isSingleTask = false;
211 
212     /**
213     Default constructor that initializes a `TaskPool` with
214     `totalCPUs` - 1 worker threads.  The minus 1 is included because the
215     main thread will also be available to do work.
216 
217     Note:  On single-core machines, the primitives provided by `TaskPool`
218            operate transparently in single-threaded mode.
219      */
220     this() {
221         this(totalCPUs - 1);
222     }
223     
224     /**
225     Allows for custom number of worker threads.
226     */
227     this(size_t nWorkers) {
228         if (nWorkers == 0)
229             nWorkers = 1;
230 
231         queueMutex = new Mutex(this);
232         waiterMutex = new Mutex();
233         workerCondition = new Condition(queueMutex);
234         waiterCondition = new Condition(waiterMutex);
235         nextThreadIndex = 0;
236 
237         pool = new ParallelismThread[nWorkers];
238         foreach (ref poolThread; pool) {
239             poolThread = new ParallelismThread(&startWorkLoop);
240             poolThread.pool = this;
241             poolThread.start();
242         }
243     }
244 
245     bool isDaemon() @property @trusted {
246         return pool[0].isDaemon;
247     }
248 
249     /// Ditto
250     void isDaemon(bool newVal) @property @trusted {
251         foreach (thread; pool) {
252             thread.isDaemon = newVal;
253         }
254     }
255 
256     // This function performs initialization for each thread that affects
257     // thread local storage and therefore must be done from within the
258     // worker thread.  It then calls executeWorkLoop().
259     private void startWorkLoop() {
260         // Initialize thread index.
261         size_t index = atomicOp!("+=")(nextThreadIndex, 1);
262         threadIndex = index - 1;
263 
264         executeWorkLoop();
265     }
266 
267     // This is the main work loop that worker threads spend their time in
268     // until they terminate.  It's also entered by non-worker threads when
269     // finish() is called with the blocking variable set to true.
270     private void executeWorkLoop() {
271         while (atomicReadUbyte(status) != PoolState.stopNow) {
272             AbstractTask task = pool[threadIndex].taskQueue.dequeue();
273             if (task is null) {
274                 if (atomicReadUbyte(status) == PoolState.finishing) {
275                     atomicSetUbyte(status, PoolState.stopNow);
276                     return;
277                 }
278             } else {
279                 doJob(task);
280             }
281         }
282     }
283 
284     private void doJob(AbstractTask job) {
285         // assert(job.taskStatus == TaskStatus.processing);
286 
287         // scope (exit) {
288         //     // if (!isSingleTask)
289         //     {
290         //         waiterLock();
291         //         scope (exit)
292         //             waiterUnlock();
293         //         notifyWaiters();
294         //     }
295         // }
296         job.run();
297     }
298 
299     private void waiterLock() {
300         if (!isSingleTask)
301             waiterMutex.lock();
302     }
303 
304     private void waiterUnlock() {
305         if (!isSingleTask)
306             waiterMutex.unlock();
307     }
308 
309     private void wait() {
310         if (!isSingleTask)
311             workerCondition.wait();
312     }
313 
314     private void notify() {
315         if (!isSingleTask)
316             workerCondition.notify();
317     }
318 
319     private void notifyAll() {
320         if (!isSingleTask)
321             workerCondition.notifyAll();
322     }
323 
324     private void waitUntilCompletion() {
325         waiterCondition.wait();
326     }
327 
328     private void notifyWaiters() {
329         if (!isSingleTask)
330             waiterCondition.notifyAll();
331     }
332 
333     void stop() @trusted {
334         // queueLock();
335         // scope(exit) queueUnlock();
336         atomicSetUbyte(status, PoolState.stopNow);
337         notifyAll();
338     }
339 
340     void finish(bool blocking = false) @trusted {
341         {
342             // queueLock();
343             // scope(exit) queueUnlock();
344             atomicCasUbyte(status, PoolState.running, PoolState.finishing);
345             notifyAll();
346         }
347         if (blocking) {
348             // Use this thread as a worker until everything is finished.
349             // stopWorkLoop();
350             // taskQueue.wakeup();
351             executeWorkLoop();
352 
353             foreach (t; pool) {
354                 // Maybe there should be something here to prevent a thread
355                 // from calling join() on itself if this function is called
356                 // from a worker thread in the same pool, but:
357                 //
358                 // 1.  Using an if statement to skip join() would result in
359                 //     finish() returning without all tasks being finished.
360                 //
361                 // 2.  If an exception were thrown, it would bubble up to the
362                 //     Task from which finish() was called and likely be
363                 //     swallowed.
364                 t.join();
365             }
366         }
367     }
368 
369     void put(int factor, AbstractTask task) {
370         int nWorkers = cast(int)pool.length;
371         if(factor<0) factor = -factor;
372         int i = factor % nWorkers;
373         // tracef("factor=%d, index=%d", factor, i);
374         pool[i].taskQueue.enqueue(task);
375     }
376 }