1 /** 2 Copyright: Copyright (c) 2016-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 // Based on Martin Nowak's lock-free package. 8 module voxelman.thread.sharedqueue; 9 10 import core.atomic; 11 import core.thread : Thread; 12 import std.experimental.allocator.mallocator; 13 import voxelman.log; 14 15 //version = DBG_QUEUE; 16 private enum PAGE_SIZE = 4096; 17 /// Single-producer single-consumer fixed size circular buffer queue. 18 shared struct SharedQueue { 19 size_t capacity; 20 21 void alloc(string debugName = null, size_t _capacity = roundPow2(PAGE_SIZE)) shared { 22 _debugName = debugName; 23 capacity = _capacity; 24 assert(capacity > 0, "Cannot have a capacity of 0."); 25 assert(roundPow2(capacity) == capacity, "The capacity must be a power of 2"); 26 _data = cast(shared ubyte[])Mallocator.instance.allocate(capacity); 27 assert(_data, "Cannot allocate memory for queue"); 28 } 29 30 void free() shared { 31 Mallocator.instance.deallocate(cast(ubyte[])_data); 32 } 33 34 @property bool empty() shared const { 35 return !length; 36 } 37 38 @property size_t length() shared const { 39 return atomicLoad!(MemoryOrder.acq)(_writePos) - atomicLoad!(MemoryOrder.acq)(_readPos); 40 } 41 42 @property size_t space() shared const { 43 return capacity - length; 44 } 45 46 @property bool full() shared const { 47 return length == capacity; 48 } 49 50 void pushItem(I)(I item) shared { 51 immutable writePosition = atomicLoad!(MemoryOrder.acq)(_writePos); 52 // space < I.sizeof 53 while (capacity - writePosition + atomicLoad!(MemoryOrder.acq)(_readPos) < I.sizeof) { 54 yield(); 55 } 56 setItem(item, writePosition); 57 atomicStore!(MemoryOrder.rel)(_writePos, writePosition + I.sizeof); 58 version(DBG_QUEUE) printTrace!"pushItem"(item); 59 } 60 61 I popItem(I)() shared { 62 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 63 64 immutable pos = atomicLoad!(MemoryOrder.acq)(_readPos); 65 I res; 66 getItem(res, pos); 67 atomicStore!(MemoryOrder.rel)(_readPos, pos + I.sizeof); 68 version(DBG_QUEUE) printTrace!"popItem"(res); 69 return res; 70 } 71 72 void popItem(I)(out I item) shared { 73 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 74 75 immutable pos = atomicLoad!(MemoryOrder.acq)(_readPos); 76 getItem(item, pos); 77 atomicStore!(MemoryOrder.rel)(_readPos, pos + I.sizeof); 78 version(DBG_QUEUE) printTrace!"popItem"(item); 79 } 80 81 I peekItem(I)() shared { 82 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 83 84 immutable pos = atomicLoad!(MemoryOrder.acq)(_readPos); 85 I res; 86 getItem(res, pos); 87 version(DBG_QUEUE) printTrace!"peekItem"(res); 88 return res; 89 } 90 91 void peekItem(I)(out I item) shared { 92 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 93 94 immutable pos = atomicLoad!(MemoryOrder.acq)(_readPos); 95 getItem(item, pos); 96 version(DBG_QUEUE) printTrace!"peekItem"(item); 97 } 98 99 void dropItem(I)() shared { 100 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 101 102 immutable pos = atomicLoad!(MemoryOrder.acq)(_readPos); 103 atomicStore!(MemoryOrder.rel)(_readPos, pos + I.sizeof); 104 version(DBG_QUEUE) printTrace!"dropItem"(); 105 } 106 107 private void getItem(I)(out I item, const size_t at) shared const { 108 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 109 ubyte[] itemData = (*cast(ubyte[I.sizeof]*)&item); 110 111 size_t start = at & (capacity - 1); 112 size_t end = (at + I.sizeof) & (capacity - 1); 113 if (end > start) 114 { 115 // item[0] v v item[$] 116 // ...........|...item...|.......... 117 // data[0] ^ start ^ ^ end ^ data[$] 118 itemData[0..$] = _data[start..end]; 119 } 120 else 121 { 122 // item[$] v item[0] v 123 // |...itemEnd...|...............|...itemStart...| 124 // _data[0] ^ end ^ start ^ ^ _data[$] 125 size_t firstPart = I.sizeof - end; 126 itemData[0..firstPart] = _data[start..$]; 127 itemData[firstPart..$] = _data[0..end]; 128 } 129 } 130 131 void setItem(I)(auto const ref I item, const size_t at) shared { 132 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 133 ubyte[] itemData = (*cast(ubyte[I.sizeof]*)&item); 134 135 size_t start = at & (capacity - 1); 136 size_t end = (at + I.sizeof) & (capacity - 1); 137 if (end > start) 138 { 139 // item[0] v v item[$] 140 // ...........|...item...|.......... 141 // data[0] ^ start ^ ^ end ^ data[$] 142 _data[start..end] = itemData[0..$]; 143 } 144 else 145 { 146 // item[$] v item[0] v 147 // |...itemEnd...|...............|...itemStart...| 148 // data[0] ^ end ^ start ^ data[$] ^ 149 size_t firstPart = I.sizeof - end; 150 _data[start..$] = itemData[0..firstPart]; 151 _data[0..end] = itemData[firstPart..$]; 152 } 153 atomicFence(); 154 } 155 156 // enter multipart message mode 157 void startMessage() shared { 158 _msgWritePos = atomicLoad!(MemoryOrder.acq)(_writePos); 159 version(DBG_QUEUE) printTrace!"startMessage"(); 160 } 161 162 // exit multipart message mode 163 void endMessage() shared { 164 atomicStore!(MemoryOrder.rel)(_writePos, _msgWritePos); 165 version(DBG_QUEUE) printTrace!"endMessage"(); 166 } 167 168 // skip to fill in later with setItem in multipart message mode 169 size_t skipMessageItem(I)() shared { 170 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 171 size_t skippedItemPos = cast(size_t)_msgWritePos; 172 // space < I.sizeof 173 while (capacity - _msgWritePos + atomicLoad!(MemoryOrder.acq)(_readPos) < I.sizeof) { 174 yield(); 175 } 176 cast(size_t)_msgWritePos += I.sizeof; 177 version(DBG_QUEUE) printTrace!"skipMessageItem"(); 178 return skippedItemPos; 179 } 180 181 // can cause dead-lock if consumer is waiting for producer. 182 // Make sure that there is enough space. Or else keep consuming 183 // push in multipart message mode 184 void pushMessagePart(I)(auto const ref I item) shared { 185 //static assert(I.sizeof <= capacity, "Item size is greater then capacity"); 186 // space < I.sizeof 187 while (capacity - _msgWritePos + atomicLoad!(MemoryOrder.acq)(_readPos) < I.sizeof) { 188 yield(); 189 } 190 setItem(item, _msgWritePos); 191 cast(size_t)_msgWritePos += I.sizeof; 192 version(DBG_QUEUE) printTrace!"pushMessagePart"(item); 193 } 194 195 static void yield() { 196 Thread.yield(); 197 //infof("yield"); 198 } 199 200 private: 201 size_t _msgWritePos; 202 size_t _writePos; 203 size_t _readPos; 204 ubyte[] _data; 205 string _debugName; 206 207 import std.concurrency : thisTid; 208 void printTrace(string funname, D)(D data) { 209 version(DBG_QUEUE) tracef("%s.%s."~funname~"(%s)\n\tmwp %s, wp %s, rp %s\n", 210 thisTid, _debugName, data, _msgWritePos, cast(size_t)_writePos, cast(size_t)_readPos); 211 } 212 void printTrace(string funname)() { 213 version(DBG_QUEUE) tracef("%s.%s."~funname~"()\n\tmwp %s, wp %s, rp %s\n", 214 thisTid, _debugName, _msgWritePos, cast(size_t)_writePos, cast(size_t)_readPos); 215 } 216 } 217 218 size_t roundPow2(size_t v) { 219 import core.bitop : bsr; 220 return v ? cast(size_t)1 << bsr(v) : 0; 221 }