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 +/