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 }