1 /// Fiber local storage 2 module mecca.reactor.fls; 3 4 // Licensed under the Boost license. Full copyright information in the AUTHORS file 5 6 import std.traits; 7 8 import mecca.log; 9 import mecca.lib.exception; 10 import mecca.reactor; 11 12 version(unittest) 13 // Unittests use more FLS 14 enum FLS_AREA_SIZE = 1024; 15 else 16 enum FLS_AREA_SIZE = 512; 17 18 struct FLSArea { 19 align( (void*).alignof ): 20 __gshared static const FLSArea flsAreaInit; 21 __gshared static int _flsOffset = 0; 22 /* thread local */ static FLSArea* thisFls; 23 24 ubyte[FLS_AREA_SIZE] data; 25 26 void reset() nothrow @safe @nogc { 27 pragma(inline, true); 28 data[] = flsAreaInit.data[]; 29 } 30 31 @notrace void switchTo() nothrow @trusted @nogc { 32 pragma(inline, true); 33 thisFls = &this; 34 } 35 36 static void switchToNone() nothrow @safe @nogc { 37 pragma(inline, true); 38 thisFls = null; 39 } 40 41 private static int alloc(T)(T initVal) { 42 // Make sure allocation is properly aligned 43 import std..string : format; 44 static assert(T.alignof <= (void*).alignof, "Cannot allocate on FLS type %s with alignement %s > ptr alignement" 45 .format(T.stringof, T.alignof)); 46 _flsOffset += T.alignof - 1; 47 _flsOffset -= _flsOffset % T.alignof; 48 49 int offset = _flsOffset; 50 _flsOffset += T.sizeof; 51 assert (_flsOffset <= data.sizeof); 52 *cast(T*)(flsAreaInit.data.ptr + offset) = initVal; 53 return offset; 54 } 55 } 56 static assert(FLSArea.alignof == (void*).alignof, "FLSArea must have same alignement as a pointer"); 57 static assert((FLSArea.data.offsetof % (void*).alignof) == 0, "FLSArea data must have same alignement as a pointer"); 58 59 private template FLSOffset(T, T initVal, string id, string file, string mod, ulong line) { 60 __gshared int FLSOffset = -1; 61 62 shared static this() { 63 static int var; 64 if( FLSOffset!=-1 ) { 65 META!"#DMDBUG issue 18868: static this ran twice for FiberLocal!(%s) defined at %s:%s"(T.stringof, file, line); 66 } else { 67 FLSOffset = FLSArea.alloc!T(initVal); 68 } 69 } 70 } 71 72 /** 73 Construct for defining new fiber local storage variables. 74 75 The file, mod and line template params should be ignored. They are used merely to ensure that each FiberLocal 76 definition is unique. 77 78 To use, alias a name to FiberLocal like so: 79 80 --- 81 alias someName = FiberLocal!(uint, 12); 82 --- 83 84 Any reference to `someName` will reference a fiber local variable of type `uint` with an init value of 12. 85 86 Note: 87 It is important to understand that `someName` actually aliases a `@property` function that returns a reference to said 88 variable. Under most circumstances this makes no difference. If you wish to take the variable's address, however, you 89 need to explicitly invoke the function, or you'll get a pointer to the function rather than the variable: 90 91 --- 92 auto ptr1 = &someName; 93 pragma(msg, typeof(ptr1)); // ref uint function() nothrow @nogc @property @trusted 94 auto ptr2 = &someName(); 95 pragma(msg, typeof(ptr2)); // uint* 96 --- 97 98 The different FLS variables are distinguished based on their template paramters. Usually this is not a problem, as the 99 source file and line number where the variable is defined is coded. Under some cases, however, this is not unique 100 enough. 101 102 --- 103 static foreach(id; ["fls1", "fls2", "someOtherFls"] ) { 104 mixin(q{alias %s = FiberLocal!int;}.format(id)); 105 } 106 --- 107 108 The above code creates three variables, called `fls1`, `fls2` and `someOtherFls`, all aliased to the same actual value. 109 This is because all three are defined on the same source line. 110 111 To solve this problem, use the `id` template argument. It does not matter what it is, so long as it is unique. The 112 following code generates three variables, as before, but all three are unique: 113 114 --- 115 static foreach(id; ["fls1", "fls2", "someOtherFls"] ) { 116 mixin(q{alias %s = FiberLocal!(int, int.init, id);}.format(id)); 117 } 118 --- 119 120 Params: 121 T = The type of the FLS variable 122 initVal = The variable initial value 123 id = Optional identifier for defining multiple FLSes from the same line of code 124 */ 125 template FiberLocal( 126 T, T initVal=T.init, string id = null, string file = __FILE_FULL_PATH__, string mod = __MODULE__, ulong line = __LINE__) 127 { 128 alias offset = FLSOffset!(T, initVal, id, file, mod, line); 129 130 @property ref T FiberLocal() @trusted { 131 ASSERT!"Calling uninitialized FLS area initialized %s offset %s FLS %s:%s(%s)"( 132 FLSArea.thisFls !is null && offset >= 0, FLSArea.thisFls !is null, offset, file, line, id); 133 return *cast(T*)(FLSArea.thisFls.data.ptr + offset); 134 } 135 } 136 137 // XXX Deprecation candidate, intentionally left undocumented 138 // Returns a $(B reference) to another fiber's FLS variable. 139 template getFiberFlsLvalue(alias FLS) { 140 static if( __traits(isSame, TemplateOf!FLS, FiberLocal) ) { 141 alias T = ReturnType!FLS; 142 alias offset = FLSOffset!(TemplateArgsOf!FLS); 143 144 T* getFiberFlsLvalue(FiberHandle fib) nothrow @nogc @notrace { 145 ReactorFiber* reactorFiber = fib.get(); 146 if( reactorFiber is null ) 147 return null; 148 149 DBG_ASSERT!"Cannot manipulate FLS of a special fiber"( !reactorFiber.flag!"SPECIAL" ); 150 ASSERT!"FLS area is null"( reactorFiber.params.flsBlock.data.ptr !is null ); 151 DBG_ASSERT!"invalid FLS offset %s"( offset>=0, offset ); 152 DBG_ASSERT!"setFiberFls offset %s out of bounds %s"(offset<FLS_AREA_SIZE, offset, FLS_AREA_SIZE); 153 return cast(T*)(reactorFiber.params.flsBlock.data.ptr + offset); 154 } 155 } else { 156 // Directly defining as static assert causes the compilation to fail on another line, resulting in a less 157 // readable error message 158 static assert(false, "getFiberFlsLvalue's template argument must be a FiberLocal"); 159 } 160 } 161 162 /// Set the FLS variable of another fiber 163 template setFiberFls(alias FLS) { 164 static if( __traits(isSame, TemplateOf!FLS, FiberLocal) ) { 165 alias T = ReturnType!FLS; 166 167 void setFiberFls(FiberHandle fib, T value) nothrow @nogc { 168 T* fls = getFiberFlsLvalue!FLS(fib); 169 170 if( fls !is null ) { 171 *fls = value; 172 } 173 } 174 } else { 175 // Directly defining as static assert causes the compilation to fail on another line, resulting in a less 176 // readable error message 177 static assert(false, "setFiberFls's template argument must be a FiberLocal"); 178 } 179 } 180 181 version (unittest) { 182 alias myFls = FiberLocal!(int, 200); 183 alias yourFls = FiberLocal!(double, 0.9); 184 } 185 186 unittest { 187 FLSArea area1; 188 FLSArea area2; 189 190 area1.reset(); 191 area2.reset(); 192 193 scope(exit) FLSArea.thisFls = null; 194 195 area1.switchTo(); 196 assert (myFls == 200); 197 assert (yourFls == 0.9); 198 199 myFls = 19; 200 yourFls = 3.14; 201 202 area2.switchTo(); 203 assert (myFls == 200); 204 assert (yourFls == 0.9); 205 206 myFls = 38; 207 yourFls = 6.28; 208 209 assert (myFls == 38); 210 211 area1.switchTo(); 212 assert (myFls == 19); 213 assert (yourFls == 3.14); 214 215 area2.switchTo(); 216 assert (yourFls == 6.28); 217 } 218 219 220 unittest { 221 align(64) struct A { 222 align(64): 223 uint a; 224 } 225 static assert( !__traits(compiles, FiberLocal!(A, "wontWork", A( 12 ))) ); 226 } 227 228 unittest { 229 import mecca.reactor.sync.event : Event; 230 231 Event sync; 232 233 void fiberBody() { 234 assert(myFls == 200); 235 sync.wait(); 236 assert(myFls == 23); 237 } 238 239 testWithReactor({ 240 auto fiber = theReactor.spawnFiber(&fiberBody); 241 theReactor.yield(); 242 setFiberFls!myFls(fiber, 23); 243 sync.set(); 244 theReactor.yield(); 245 theReactor.yield(); 246 assert(myFls == 200); 247 }); 248 } 249 250 unittest { 251 import std.format; 252 static foreach(id; ["fls1", "fls2"] ) { 253 mixin(q{alias %s = FiberLocal!(int, 0, id);}.format(id)); 254 } 255 256 testWithReactor({ 257 fls1 = 12; 258 assert(fls2 == 0); 259 }); 260 } 261 262 /+ 263 // Check the compilation error message when passing wrong type to setFiberFls 264 unittest { 265 template NotFiberLocal( 266 T, T initVal=T.init, string id = null, string file = __FILE_FULL_PATH__, string mod = __MODULE__, ulong line = __LINE__) 267 { 268 } 269 270 alias notFls = NotFiberLocal!(uint); 271 272 auto t = setFiberFls!notFls(FiberHandle(), 12); 273 } 274 +/ 275 276 /+ 277 unittest { 278 alias someName = FiberLocal!(uint, 12); 279 280 auto ptr1 = &someName; 281 pragma(msg, typeof(ptr1)); 282 auto ptr2 = &someName(); 283 pragma(msg, typeof(ptr2)); 284 } 285 +/