1 module hunt.concurrency.SimpleQueue;
2 
3 import hunt.Exceptions;
4 import hunt.logging.ConsoleLogger;
5 
6 import core.thread;
7 import core.sync.semaphore : Semaphore;
8 import core.sync.condition;
9 import core.sync.mutex : Mutex;
10 import core.atomic;
11 
12 import std.datetime;
13 
14 // ported from https://github.com/qznc/d-queues
15 
16 /** Implementations based on the paper
17     "Simple, fast, and practical non-blocking and blocking concurrent queue algorithms"
18     by Maged and Michael. "*/
19 
20 /** Basic interface for all queues implemented here.
21     Is an input and output range. 
22 */
23 interface Queue(T) {
24     /** Atomically put one element into the queue. */
25     void enqueue(T t);
26 
27     /** Atomically take one element from the queue.
28       Wait blocking or spinning. */
29     T dequeue();
30 
31     /**
32       If at least one element is in the queue,
33       atomically take one element from the queue
34       store it into e, and return true.
35       Otherwise return false; */
36     bool tryDequeue(out T e);
37 }
38 
39 private class QueueNode(T) {
40     QueueNode!T nxt;
41     T value;
42 
43     this() {} 
44 
45     this(T value) {
46         this.value = value;
47     }
48 }
49 
50 /** blocking multi-producer multi-consumer queue  */
51 class BlockingQueue(T) : Queue!T {
52     private QueueNode!T head;
53     private QueueNode!T tail;
54     private Mutex head_lock;
55     private Mutex tail_lock;
56     private shared bool isWaking = false;
57 
58     /** Wait queue for waiting takes */
59     private Condition notEmpty;
60 
61     this() {
62         auto n = new QueueNode!T();
63         this.head = this.tail = n;
64         this.head_lock = new Mutex();
65         this.tail_lock = new Mutex();
66         notEmpty = new Condition(head_lock);
67     }
68 
69     void enqueue(T t) {
70         auto end = new QueueNode!T();
71         this.tail_lock.lock();
72         scope (exit)
73             this.tail_lock.unlock();
74         auto tl = this.tail;
75         this.tail = end;
76         tl.value = t;
77         atomicFence();
78         tl.nxt = end; // accessible to dequeue
79         notEmpty.notify();
80     }
81 
82     T dequeue() {
83         this.head_lock.lock();
84         scope (exit)
85             this.head_lock.unlock();
86         while (true) { // FIXME non-blocking!
87             auto hd = this.head;
88             auto scnd = hd.nxt;
89             if (scnd !is null) {
90                 this.head = scnd;
91                 return hd.value;
92             } else {
93                 if(isWaking)
94                     return T.init;
95                 notEmpty.wait();
96             }
97         }
98         assert(0);
99     }
100 
101     bool tryDequeue(out T e) {
102         this.head_lock.lock();
103         scope (exit)
104             this.head_lock.unlock();
105         auto hd = this.head;
106         auto scnd = hd.nxt;
107         if (scnd !is null) {
108             this.head = scnd;
109             e = hd.value;
110             return true;
111         }
112         return false;
113     }
114 
115     bool isEmpty() {
116         return this.head.nxt is null;
117     }
118 
119     void clear() {
120         this.head_lock.lock();
121         scope (exit)
122             this.head_lock.unlock();
123         
124         auto n = new QueueNode!T();
125         this.head = this.tail = n;
126     }
127 
128     void wakeup() {
129         if(cas(&isWaking, false, true))
130             notEmpty.notify();
131     }
132 }
133 
134 /** non-blocking multi-producer multi-consumer queue  */
135 class NonBlockingQueue(T) : Queue!T {
136     private shared(QueueNode!T) head;
137     private shared(QueueNode!T) tail;
138     private shared bool isWaking = false;
139 
140     this() {
141         shared n = new QueueNode!T();
142         this.head = this.tail = n;
143     }
144 
145     void enqueue(T t) {
146         shared end = new QueueNode!T();
147         end.value = cast(shared)t;
148         while (true) {
149             auto tl = tail;
150             auto cur = tl.nxt;
151             if (cur !is null) {
152                 // obsolete tail, try update
153                 cas(&this.tail, tl, cur);
154                 continue;
155             }
156             shared(QueueNode!T) dummy = null;
157             if (cas(&tl.nxt, dummy, end)) {
158                 // successfull enqueued new end node
159                 break;
160             }
161         }
162     }
163 
164     T dequeue() {
165         T e = void;
166         while (!tryDequeue(e)) {
167             Thread.yield();
168         }
169         // tryDequeue(e);
170         return e;
171     }
172 
173     bool tryDequeue(out T e) {
174         auto dummy = this.head;
175         auto tl = this.tail;
176         auto nxt = dummy.nxt;
177 
178         if(nxt is null)
179             return false;
180         
181         if (cas(&this.head, dummy, nxt)) {
182             e = cast(T)nxt.value;
183             return true;
184         }
185         return tryDequeue(e);
186     }
187 
188     bool isEmpty() {
189         return this.head.nxt is null;
190     }
191 
192     void clear() {        
193         shared n = new QueueNode!T();
194         this.head = this.tail = n;
195     }
196 }
197 
198 
199 /**
200  * 
201  */
202 class SimpleQueue(T) : Queue!T {
203     private QueueNode!T head;
204     private QueueNode!T tail;
205 
206     this() {
207         auto n = new QueueNode!T();
208         this.head = this.tail = n;
209     }
210 
211     void enqueue(T t) {
212         auto end = new QueueNode!T(t);
213         
214         auto tl = this.tail;
215         this.tail = end;
216         tl.nxt = end; // acces
217     }
218 
219     T dequeue() {
220         T e = void;
221         while (!tryDequeue(e)) {
222             Thread.yield();
223             version(HUNT_DANGER_DEBUG) warning("Running here");
224         }
225         return e;
226     }
227 
228     T dequeue(Duration timeout) {
229         T e = void;
230         auto start = Clock.currTime;
231         bool r = tryDequeue(e);
232         while (!r && Clock.currTime < start + timeout) {
233             debug {
234                 Duration dur = Clock.currTime - start;
235                 if(dur > 15.seconds) {
236                     warningf("There is no element available in %s", dur);
237                 }
238             }
239             Thread.yield();
240             r = tryDequeue(e);
241         }
242 
243         if (!r) {
244             throw new TimeoutException("Timeout in " ~ timeout.toString());
245         }
246         return e;
247     }
248 
249 
250     bool tryDequeue(out T e) {
251         auto nxt = this.head.nxt;
252         if(nxt is null)
253             return false;
254         
255         this.head = nxt;
256         e = cast(T)nxt.value;
257         return true;
258     }
259 
260 
261     bool isEmpty() {
262         return this.head.nxt is null;
263     }
264 
265     void clear() {        
266         auto n = new QueueNode!T();
267         this.head = this.tail = n;
268     }
269 }