diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-14 14:15:01 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-14 14:15:01 +0200 |
commit | 9589d1941671818f78d9894cfc9485054d62d122 (patch) | |
tree | c804998ba5cf89b75cb3d27052ee469fd4986595 /src | |
parent | 1013970853e6acfd60591a89ae08cc40c64bee06 (diff) | |
download | lanes-9589d1941671818f78d9894cfc9485054d62d122.tar.gz lanes-9589d1941671818f78d9894cfc9485054d62d122.tar.bz2 lanes-9589d1941671818f78d9894cfc9485054d62d122.zip |
Move Lane implementation in a separate file
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 2 | ||||
-rw-r--r-- | src/cancel.cpp | 2 | ||||
-rw-r--r-- | src/lane.cpp | 916 | ||||
-rw-r--r-- | src/lane.h (renamed from src/lanes_private.h) | 36 | ||||
-rw-r--r-- | src/lanes.cpp | 894 | ||||
-rw-r--r-- | src/linda.cpp | 2 | ||||
-rw-r--r-- | src/state.cpp | 2 | ||||
-rw-r--r-- | src/tracker.cpp | 2 | ||||
-rw-r--r-- | src/universe.cpp | 2 |
9 files changed, 955 insertions, 903 deletions
diff --git a/src/Makefile b/src/Makefile index fddc45e..a01f7c3 100644 --- a/src/Makefile +++ b/src/Makefile | |||
@@ -7,7 +7,7 @@ | |||
7 | 7 | ||
8 | MODULE=lanes | 8 | MODULE=lanes |
9 | 9 | ||
10 | SRC=cancel.cpp compat.cpp deep.cpp intercopycontext.cpp keeper.cpp lanes.cpp linda.cpp lindafactory.cpp state.cpp threading.cpp tools.cpp tracker.cpp universe.cpp | 10 | SRC=cancel.cpp compat.cpp deep.cpp intercopycontext.cpp keeper.cpp lane.cpp lanes.cpp linda.cpp lindafactory.cpp state.cpp threading.cpp tools.cpp tracker.cpp universe.cpp |
11 | 11 | ||
12 | OBJ=$(SRC:.cpp=.o) | 12 | OBJ=$(SRC:.cpp=.o) |
13 | 13 | ||
diff --git a/src/cancel.cpp b/src/cancel.cpp index b297c85..d0a1349 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp | |||
@@ -35,7 +35,7 @@ THE SOFTWARE. | |||
35 | 35 | ||
36 | #include "cancel.h" | 36 | #include "cancel.h" |
37 | 37 | ||
38 | #include "lanes_private.h" | 38 | #include "lane.h" |
39 | 39 | ||
40 | // ################################################################################################# | 40 | // ################################################################################################# |
41 | // ################################################################################################# | 41 | // ################################################################################################# |
diff --git a/src/lane.cpp b/src/lane.cpp new file mode 100644 index 0000000..a03412d --- /dev/null +++ b/src/lane.cpp | |||
@@ -0,0 +1,916 @@ | |||
1 | /* | ||
2 | =============================================================================== | ||
3 | |||
4 | Copyright (C) 2024 Benoit Germain <bnt.germain@gmail.com> | ||
5 | |||
6 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
7 | of this software and associated documentation files (the "Software"), to deal | ||
8 | in the Software without restriction, including without limitation the rights | ||
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
10 | copies of the Software, and to permit persons to whom the Software is | ||
11 | furnished to do so, subject to the following conditions: | ||
12 | |||
13 | The above copyright notice and this permission notice shall be included in | ||
14 | all copies or substantial portions of the Software. | ||
15 | |||
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
22 | THE SOFTWARE. | ||
23 | |||
24 | =============================================================================== | ||
25 | */ | ||
26 | |||
27 | #include "lane.h" | ||
28 | |||
29 | #include "intercopycontext.h" | ||
30 | #include "tools.h" | ||
31 | #include "threading.h" | ||
32 | |||
33 | // ################################################################################################# | ||
34 | |||
35 | /* Do you want full call stacks, or just the line where the error happened? | ||
36 | * | ||
37 | * TBD: The full stack feature does not seem to work (try 'make error'). | ||
38 | */ | ||
39 | #define ERROR_FULL_STACK 1 // must be either 0 or 1 as we do some index arithmetics with it! | ||
40 | |||
41 | // ################################################################################################# | ||
42 | // ######################################### Lua API ############################################### | ||
43 | // ################################################################################################# | ||
44 | |||
45 | LUAG_FUNC(get_debug_threadname) | ||
46 | { | ||
47 | Lane* const _lane{ ToLane(L_, 1) }; | ||
48 | luaL_argcheck(L_, lua_gettop(L_) == 1, 2, "too many arguments"); | ||
49 | lua_pushstring(L_, _lane->debugName); | ||
50 | return 1; | ||
51 | } | ||
52 | |||
53 | // ################################################################################################# | ||
54 | |||
55 | // void= finalizer( finalizer_func ) | ||
56 | // | ||
57 | // finalizer_func( [err, stack_tbl] ) | ||
58 | // | ||
59 | // Add a function that will be called when exiting the lane, either via | ||
60 | // normal return or an error. | ||
61 | // | ||
62 | LUAG_FUNC(set_finalizer) | ||
63 | { | ||
64 | luaL_argcheck(L_, lua_isfunction(L_, 1), 1, "finalizer should be a function"); | ||
65 | luaL_argcheck(L_, lua_gettop(L_) == 1, 1, "too many arguments"); | ||
66 | STACK_GROW(L_, 3); | ||
67 | // Get the current finalizer table (if any), create one if it doesn't exist | ||
68 | std::ignore = kFinalizerRegKey.getSubTable(L_, 1, 0); // L_: finalizer {finalisers} | ||
69 | // must cast to int, not lua_Integer, because LuaJIT signature of lua_rawseti is not the same as PUC-Lua. | ||
70 | int const _idx{ static_cast<int>(lua_rawlen(L_, -1) + 1) }; | ||
71 | lua_pushvalue(L_, 1); // L_: finalizer {finalisers} finalizer | ||
72 | lua_rawseti(L_, -2, _idx); // L_: finalizer {finalisers} | ||
73 | // no need to adjust the stack, Lua does this for us | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | // ################################################################################################# | ||
78 | |||
79 | #if ERROR_FULL_STACK | ||
80 | LUAG_FUNC(set_error_reporting) | ||
81 | { | ||
82 | luaL_checktype(L_, 1, LUA_TSTRING); | ||
83 | char const* _mode{ lua_tostring(L_, 1) }; | ||
84 | lua_pushliteral(L_, "extended"); | ||
85 | bool const _extended{ strcmp(_mode, "extended") == 0 }; | ||
86 | bool const _basic{ strcmp(_mode, "basic") == 0 }; | ||
87 | if (!_extended && !_basic) { | ||
88 | raise_luaL_error(L_, "unsupported error reporting model %s", _mode); | ||
89 | } | ||
90 | |||
91 | kExtendedStackTraceRegKey.setValue(L_, [extended = _extended](lua_State* L_) { lua_pushboolean(L_, extended ? 1 : 0); }); | ||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | #endif // ERROR_FULL_STACK | ||
96 | |||
97 | // ################################################################################################# | ||
98 | |||
99 | // upvalue #1 is the lane userdata | ||
100 | LUAG_FUNC(set_debug_threadname) | ||
101 | { | ||
102 | // C s_lane structure is a light userdata upvalue | ||
103 | Lane* const _lane{ lua_tolightuserdata<Lane>(L_, lua_upvalueindex(1)) }; | ||
104 | LUA_ASSERT(L_, L_ == _lane->L); // this function is exported in a lane's state, therefore it is callable only from inside the Lane's state | ||
105 | lua_settop(L_, 1); | ||
106 | STACK_CHECK_START_REL(L_, 0); | ||
107 | _lane->changeDebugName(-1); | ||
108 | STACK_CHECK(L_, 0); | ||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | // ################################################################################################# | ||
113 | |||
114 | //--- | ||
115 | // [...] | [nil, err_any, stack_tbl]= thread_join( lane_ud [, wait_secs=-1] ) | ||
116 | // | ||
117 | // timeout: returns nil | ||
118 | // done: returns return values (0..N) | ||
119 | // error: returns nil + error value [+ stack table] | ||
120 | // cancelled: returns nil | ||
121 | // | ||
122 | LUAG_FUNC(thread_join) | ||
123 | { | ||
124 | Lane* const _lane{ ToLane(L_, 1) }; | ||
125 | lua_State* const _L2{ _lane->L }; | ||
126 | |||
127 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; | ||
128 | if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion | ||
129 | lua_Duration const duration{ lua_tonumber(L_, 2) }; | ||
130 | if (duration.count() >= 0.0) { | ||
131 | _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration); | ||
132 | } else { | ||
133 | raise_luaL_argerror(L_, 2, "duration cannot be < 0"); | ||
134 | } | ||
135 | |||
136 | } else if (!lua_isnoneornil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key | ||
137 | raise_luaL_argerror(L_, 2, "incorrect duration type"); | ||
138 | } | ||
139 | |||
140 | bool const done{ !_lane->thread.joinable() || _lane->waitForCompletion(_until) }; | ||
141 | lua_settop(L_, 1); // L_: lane | ||
142 | if (!done || !_L2) { | ||
143 | lua_pushnil(L_); // L_: lane nil | ||
144 | lua_pushliteral(L_, "timeout"); // L_: lane nil "timeout" | ||
145 | return 2; | ||
146 | } | ||
147 | |||
148 | STACK_CHECK_START_REL(L_, 0); // L_: lane | ||
149 | // Thread is Done/Error/Cancelled; all ours now | ||
150 | |||
151 | int _ret{ 0 }; | ||
152 | // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed | ||
153 | // so store it in the userdata uservalue at a key that can't possibly collide | ||
154 | _lane->securizeDebugName(L_); | ||
155 | switch (_lane->status) { | ||
156 | case Lane::Done: | ||
157 | { | ||
158 | int const _n{ lua_gettop(_L2) }; // whole L2 stack | ||
159 | if ( | ||
160 | (_n > 0) && | ||
161 | (InterCopyContext{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }.inter_move(_n) != InterCopyResult::Success) | ||
162 | ) { // L_: lane results L2: | ||
163 | raise_luaL_error(L_, "tried to copy unsupported types"); | ||
164 | } | ||
165 | _ret = _n; | ||
166 | } | ||
167 | break; | ||
168 | |||
169 | case Lane::Error: | ||
170 | { | ||
171 | int const _n{ lua_gettop(_L2) }; // L_: lane L2: "err" [trace] | ||
172 | STACK_GROW(L_, 3); | ||
173 | lua_pushnil(L_); // L_: lane nil | ||
174 | // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ... | ||
175 | InterCopyContext _c{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }; | ||
176 | if (_c.inter_move(_n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2: | ||
177 | raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -_n)); | ||
178 | } | ||
179 | _ret = 1 + _n; | ||
180 | } | ||
181 | break; | ||
182 | |||
183 | case Lane::Cancelled: | ||
184 | _ret = 0; | ||
185 | break; | ||
186 | |||
187 | default: | ||
188 | DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", _lane->status)); | ||
189 | LUA_ASSERT(L_, false); | ||
190 | _ret = 0; | ||
191 | } | ||
192 | lua_close(_L2); | ||
193 | _lane->L = nullptr; | ||
194 | STACK_CHECK(L_, _ret); | ||
195 | return _ret; | ||
196 | } | ||
197 | |||
198 | // ################################################################################################# | ||
199 | |||
200 | // lane:__index(key,usr) -> value | ||
201 | // | ||
202 | // If key is found in the environment, return it | ||
203 | // If key is numeric, wait until the thread returns and populate the environment with the return values | ||
204 | // If the return values signal an error, propagate it | ||
205 | // If key is "status" return the thread status | ||
206 | // Else raise an error | ||
207 | LUAG_FUNC(thread_index) | ||
208 | { | ||
209 | static constexpr int kSelf{ 1 }; | ||
210 | static constexpr int kKey{ 2 }; | ||
211 | Lane* const _lane{ ToLane(L_, kSelf) }; | ||
212 | LUA_ASSERT(L_, lua_gettop(L_) == 2); | ||
213 | |||
214 | STACK_GROW(L_, 8); // up to 8 positions are needed in case of error propagation | ||
215 | |||
216 | // If key is numeric, wait until the thread returns and populate the environment with the return values | ||
217 | if (lua_type(L_, kKey) == LUA_TNUMBER) { | ||
218 | static constexpr int kUsr{ 3 }; | ||
219 | // first, check that we don't already have an environment that holds the requested value | ||
220 | { | ||
221 | // If key is found in the uservalue, return it | ||
222 | lua_getiuservalue(L_, kSelf, 1); | ||
223 | lua_pushvalue(L_, kKey); | ||
224 | lua_rawget(L_, kUsr); | ||
225 | if (!lua_isnil(L_, -1)) { | ||
226 | return 1; | ||
227 | } | ||
228 | lua_pop(L_, 1); | ||
229 | } | ||
230 | { | ||
231 | // check if we already fetched the values from the thread or not | ||
232 | lua_pushinteger(L_, 0); | ||
233 | lua_rawget(L_, kUsr); | ||
234 | bool const _fetched{ !lua_isnil(L_, -1) }; | ||
235 | lua_pop(L_, 1); // back to our 2 args + uservalue on the stack | ||
236 | if (!_fetched) { | ||
237 | lua_pushinteger(L_, 0); | ||
238 | lua_pushboolean(L_, 1); | ||
239 | lua_rawset(L_, kUsr); | ||
240 | // wait until thread has completed | ||
241 | lua_pushcfunction(L_, LG_thread_join); | ||
242 | lua_pushvalue(L_, kSelf); | ||
243 | lua_call(L_, 1, LUA_MULTRET); // all return values are on the stack, at slots 4+ | ||
244 | switch (_lane->status) { | ||
245 | default: | ||
246 | // this is an internal error, we probably never get here | ||
247 | lua_settop(L_, 0); | ||
248 | lua_pushliteral(L_, "Unexpected status: "); | ||
249 | _lane->pushThreadStatus(L_); | ||
250 | lua_concat(L_, 2); | ||
251 | raise_lua_error(L_); | ||
252 | [[fallthrough]]; // fall through if we are killed, as we got nil, "killed" on the stack | ||
253 | |||
254 | case Lane::Done: // got regular return values | ||
255 | { | ||
256 | int const _nvalues{ lua_gettop(L_) - 3 }; | ||
257 | for (int _i = _nvalues; _i > 0; --_i) { | ||
258 | // pop the last element of the stack, to store it in the uservalue at its proper index | ||
259 | lua_rawseti(L_, kUsr, _i); | ||
260 | } | ||
261 | } | ||
262 | break; | ||
263 | |||
264 | case Lane::Error: // got 3 values: nil, errstring, callstack table | ||
265 | // me[-2] could carry the stack table, but even | ||
266 | // me[-1] is rather unnecessary (and undocumented); | ||
267 | // use ':join()' instead. --AKa 22-Jan-2009 | ||
268 | LUA_ASSERT(L_, lua_isnil(L_, 4) && !lua_isnil(L_, 5) && lua_istable(L_, 6)); | ||
269 | // store errstring at key -1 | ||
270 | lua_pushnumber(L_, -1); | ||
271 | lua_pushvalue(L_, 5); | ||
272 | lua_rawset(L_, kUsr); | ||
273 | break; | ||
274 | |||
275 | case Lane::Cancelled: | ||
276 | // do nothing | ||
277 | break; | ||
278 | } | ||
279 | } | ||
280 | lua_settop(L_, 3); // L_: self KEY ENV | ||
281 | int const _key{ static_cast<int>(lua_tointeger(L_, kKey)) }; | ||
282 | if (_key != -1) { | ||
283 | lua_pushnumber(L_, -1); // L_: self KEY ENV -1 | ||
284 | lua_rawget(L_, kUsr); // L_: self KEY ENV "error"|nil | ||
285 | if (!lua_isnil(L_, -1)) { // L_: an error was stored | ||
286 | // Note: Lua 5.1 interpreter is not prepared to show | ||
287 | // non-string errors, so we use 'tostring()' here | ||
288 | // to get meaningful output. --AKa 22-Jan-2009 | ||
289 | // | ||
290 | // Also, the stack dump we get is no good; it only | ||
291 | // lists our internal Lanes functions. There seems | ||
292 | // to be no way to switch it off, though. | ||
293 | // | ||
294 | // Level 3 should show the line where 'h[x]' was read | ||
295 | // but this only seems to work for string messages | ||
296 | // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 | ||
297 | lua_getmetatable(L_, kSelf); // L_: self KEY ENV "error" mt | ||
298 | lua_getfield(L_, -1, "cached_error"); // L_: self KEY ENV "error" mt error() | ||
299 | lua_getfield(L_, -2, "cached_tostring"); // L_: self KEY ENV "error" mt error() tostring() | ||
300 | lua_pushvalue(L_, 4); // L_: self KEY ENV "error" mt error() tostring() "error" | ||
301 | lua_call(L_, 1, 1); // tostring(errstring) -- just in case // L_: self KEY ENV "error" mt error() "error" | ||
302 | lua_pushinteger(L_, 3); // L_: self KEY ENV "error" mt error() "error" 3 | ||
303 | lua_call(L_, 2, 0); // error(tostring(errstring), 3) -> doesn't return // L_: self KEY ENV "error" mt | ||
304 | } else { | ||
305 | lua_pop(L_, 1); // L_: self KEY ENV | ||
306 | } | ||
307 | } | ||
308 | lua_rawgeti(L_, kUsr, _key); | ||
309 | } | ||
310 | return 1; | ||
311 | } | ||
312 | if (lua_type(L_, kKey) == LUA_TSTRING) { | ||
313 | char const* const _keystr{ lua_tostring(L_, kKey) }; | ||
314 | lua_settop(L_, 2); // keep only our original arguments on the stack | ||
315 | if (strcmp(_keystr, "status") == 0) { | ||
316 | _lane->pushThreadStatus(L_); // push the string representing the status | ||
317 | return 1; | ||
318 | } | ||
319 | // return self.metatable[key] | ||
320 | lua_getmetatable(L_, kSelf); // L_: self KEY mt | ||
321 | lua_replace(L_, -3); // L_: mt KEY | ||
322 | lua_rawget(L_, -2); // L_: mt value | ||
323 | // only "cancel" and "join" are registered as functions, any other string will raise an error | ||
324 | if (!lua_iscfunction(L_, -1)) { | ||
325 | raise_luaL_error(L_, "can't index a lane with '%s'", _keystr); | ||
326 | } | ||
327 | return 1; | ||
328 | } | ||
329 | // unknown key | ||
330 | lua_getmetatable(L_, kSelf); // L_: mt | ||
331 | lua_getfield(L_, -1, "cached_error"); // L_: mt error | ||
332 | lua_pushliteral(L_, "Unknown key: "); // L_: mt error "Unknown key: " | ||
333 | lua_pushvalue(L_, kKey); // L_: mt error "Unknown key: " k | ||
334 | lua_concat(L_, 2); // L_: mt error "Unknown key: <k>" | ||
335 | lua_call(L_, 1, 0); // error( "Unknown key: " .. key) -> doesn't return // L_: mt | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | // ################################################################################################# | ||
340 | // ######################################## Utilities ############################################## | ||
341 | // ################################################################################################# | ||
342 | |||
343 | #if USE_DEBUG_SPEW() | ||
344 | // can't use direct LUA_x errcode indexing because the sequence is not the same between Lua 5.1 and 5.2 :-( | ||
345 | // LUA_ERRERR doesn't have the same value | ||
346 | struct errcode_name | ||
347 | { | ||
348 | int code; | ||
349 | char const* name; | ||
350 | }; | ||
351 | |||
352 | static struct errcode_name s_errcodes[] = { | ||
353 | { LUA_OK, "LUA_OK" }, | ||
354 | { LUA_YIELD, "LUA_YIELD" }, | ||
355 | { LUA_ERRRUN, "LUA_ERRRUN" }, | ||
356 | { LUA_ERRSYNTAX, "LUA_ERRSYNTAX" }, | ||
357 | { LUA_ERRMEM, "LUA_ERRMEM" }, | ||
358 | { LUA_ERRGCMM, "LUA_ERRGCMM" }, | ||
359 | { LUA_ERRERR, "LUA_ERRERR" }, | ||
360 | }; | ||
361 | static char const* get_errcode_name(int _code) | ||
362 | { | ||
363 | for (errcode_name const& _entry : s_errcodes) { | ||
364 | if (_entry.code == _code) { | ||
365 | return _entry.name; | ||
366 | } | ||
367 | } | ||
368 | return "<nullptr>"; | ||
369 | } | ||
370 | #endif // USE_DEBUG_SPEW() | ||
371 | |||
372 | // ################################################################################################# | ||
373 | |||
374 | /* | ||
375 | * str= lane_error( error_val|str ) | ||
376 | * | ||
377 | * Called if there's an error in some lane; add call stack to error message | ||
378 | * just like 'lua.c' normally does. | ||
379 | * | ||
380 | * ".. will be called with the error message and its return value will be the | ||
381 | * message returned on the stack by lua_pcall." | ||
382 | * | ||
383 | * Note: Rather than modifying the error message itself, it would be better | ||
384 | * to provide the call stack (as string) completely separated. This would | ||
385 | * work great with non-string error values as well (current system does not). | ||
386 | * (This is NOT possible with the Lua 5.1 'lua_pcall()'; we could of course | ||
387 | * implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) | ||
388 | * --AKa 22-Jan-2009 | ||
389 | */ | ||
390 | #if ERROR_FULL_STACK | ||
391 | |||
392 | // xxh64 of string "kStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
393 | static constexpr RegistryUniqueKey kStackTraceRegKey{ 0x3F327747CACAA904ull }; | ||
394 | |||
395 | [[nodiscard]] static int lane_error(lua_State* L_) | ||
396 | { | ||
397 | // error message (any type) | ||
398 | STACK_CHECK_START_ABS(L_, 1); // L_: some_error | ||
399 | |||
400 | // Don't do stack survey for cancelled lanes. | ||
401 | // | ||
402 | if (kCancelError.equals(L_, 1)) { | ||
403 | return 1; // just pass on | ||
404 | } | ||
405 | |||
406 | STACK_GROW(L_, 3); | ||
407 | bool const _extended{ kExtendedStackTraceRegKey.readBoolValue(L_) }; | ||
408 | STACK_CHECK(L_, 1); | ||
409 | |||
410 | // Place stack trace at 'registry[kStackTraceRegKey]' for the 'lua_pcall()' | ||
411 | // caller to fetch. This bypasses the Lua 5.1 limitation of only one | ||
412 | // return value from error handler to 'lua_pcall()' caller. | ||
413 | |||
414 | // It's adequate to push stack trace as a table. This gives the receiver | ||
415 | // of the stack best means to format it to their liking. Also, it allows | ||
416 | // us to add more stack info later, if needed. | ||
417 | // | ||
418 | // table of { "sourcefile.lua:<line>", ... } | ||
419 | // | ||
420 | lua_newtable(L_); // L_: some_error {} | ||
421 | |||
422 | // Best to start from level 1, but in some cases it might be a C function | ||
423 | // and we don't get '.currentline' for that. It's okay - just keep level | ||
424 | // and table index growing separate. --AKa 22-Jan-2009 | ||
425 | // | ||
426 | lua_Debug _ar; | ||
427 | for (int _n = 1; lua_getstack(L_, _n, &_ar); ++_n) { | ||
428 | lua_getinfo(L_, _extended ? "Sln" : "Sl", &_ar); | ||
429 | if (_extended) { | ||
430 | lua_newtable(L_); // L_: some_error {} {} | ||
431 | |||
432 | lua_pushstring(L_, _ar.source); // L_: some_error {} {} source | ||
433 | lua_setfield(L_, -2, "source"); // L_: some_error {} {} | ||
434 | |||
435 | lua_pushinteger(L_, _ar.currentline); // L_: some_error {} {} currentline | ||
436 | lua_setfield(L_, -2, "currentline"); // L_: some_error {} {} | ||
437 | |||
438 | lua_pushstring(L_, _ar.name); // L_: some_error {} {} name | ||
439 | lua_setfield(L_, -2, "name"); // L_: some_error {} {} | ||
440 | |||
441 | lua_pushstring(L_, _ar.namewhat); // L_: some_error {} {} namewhat | ||
442 | lua_setfield(L_, -2, "namewhat"); // L_: some_error {} {} | ||
443 | |||
444 | lua_pushstring(L_, _ar.what); // L_: some_error {} {} what | ||
445 | lua_setfield(L_, -2, "what"); // L_: some_error {} {} | ||
446 | } else if (_ar.currentline > 0) { | ||
447 | lua_pushfstring(L_, "%s:%d", _ar.short_src, _ar.currentline); // L_: some_error {} "blah:blah" | ||
448 | } else { | ||
449 | lua_pushfstring(L_, "%s:?", _ar.short_src); // L_: some_error {} "blah" | ||
450 | } | ||
451 | lua_rawseti(L_, -2, static_cast<lua_Integer>(_n)); // L_: some_error {} | ||
452 | } | ||
453 | |||
454 | // store the stack trace table in the registry | ||
455 | kStackTraceRegKey.setValue(L_, [](lua_State* L_) { lua_insert(L_, -2); }); // L_: some_error | ||
456 | |||
457 | STACK_CHECK(L_, 1); | ||
458 | return 1; // the untouched error value | ||
459 | } | ||
460 | #endif // ERROR_FULL_STACK | ||
461 | |||
462 | // ################################################################################################# | ||
463 | // ########################################## Finalizer ############################################ | ||
464 | // ################################################################################################# | ||
465 | |||
466 | static void push_stack_trace(lua_State* L_, int rc_, int stk_base_) | ||
467 | { | ||
468 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry | ||
469 | switch (rc_) { | ||
470 | case LUA_OK: // no error, body return values are on the stack | ||
471 | break; | ||
472 | |||
473 | case LUA_ERRRUN: // cancellation or a runtime error | ||
474 | #if ERROR_FULL_STACK // when ERROR_FULL_STACK, we installed a handler | ||
475 | { | ||
476 | STACK_CHECK_START_REL(L_, 0); | ||
477 | // fetch the call stack table from the registry where the handler stored it | ||
478 | STACK_GROW(L_, 1); | ||
479 | // yields nil if no stack was generated (in case of cancellation for example) | ||
480 | kStackTraceRegKey.pushValue(L_); // L_: err trace|nil | ||
481 | STACK_CHECK(L_, 1); | ||
482 | |||
483 | // For cancellation the error message is kCancelError, and a stack trace isn't placed | ||
484 | // For other errors, the message can be whatever was thrown, and we should have a stack trace table | ||
485 | LUA_ASSERT(L_, lua_type(L_, 1 + stk_base_) == (kCancelError.equals(L_, stk_base_) ? LUA_TNIL : LUA_TTABLE)); | ||
486 | // Just leaving the stack trace table on the stack is enough to get it through to the master. | ||
487 | break; | ||
488 | } | ||
489 | #else // !ERROR_FULL_STACK | ||
490 | [[fallthrough]]; // fall through if not ERROR_FULL_STACK | ||
491 | #endif // !ERROR_FULL_STACK | ||
492 | |||
493 | case LUA_ERRMEM: // memory allocation error (handler not called) | ||
494 | case LUA_ERRERR: // error while running the error handler (if any, for example an out-of-memory condition) | ||
495 | default: | ||
496 | // we should have a single value which is either a string (the error message) or kCancelError | ||
497 | LUA_ASSERT(L_, (lua_gettop(L_) == stk_base_) && ((lua_type(L_, stk_base_) == LUA_TSTRING) || kCancelError.equals(L_, stk_base_))); | ||
498 | break; | ||
499 | } | ||
500 | } | ||
501 | |||
502 | // ################################################################################################# | ||
503 | //--- | ||
504 | // Run finalizers - if any - with the given parameters | ||
505 | // | ||
506 | // If 'rc' is nonzero, error message and stack index (the latter only when ERROR_FULL_STACK == 1) are available as: | ||
507 | // [-1]: stack trace (table) | ||
508 | // [-2]: error message (any type) | ||
509 | // | ||
510 | // Returns: | ||
511 | // 0 if finalizers were run without error (or there were none) | ||
512 | // LUA_ERRxxx return code if any of the finalizers failed | ||
513 | // | ||
514 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. | ||
515 | // | ||
516 | |||
517 | [[nodiscard]] static int run_finalizers(lua_State* L_, int lua_rc_) | ||
518 | { | ||
519 | kFinalizerRegKey.pushValue(L_); // L_: ... finalizers? | ||
520 | if (lua_isnil(L_, -1)) { | ||
521 | lua_pop(L_, 1); | ||
522 | return 0; // no finalizers | ||
523 | } | ||
524 | |||
525 | STACK_GROW(L_, 5); | ||
526 | |||
527 | int const _finalizers_index{ lua_gettop(L_) }; | ||
528 | int const _err_handler_index{ ERROR_FULL_STACK ? (lua_pushcfunction(L_, lane_error), lua_gettop(L_)) : 0 }; | ||
529 | |||
530 | int rc{ LUA_OK }; | ||
531 | for (int n = static_cast<int>(lua_rawlen(L_, _finalizers_index)); n > 0; --n) { | ||
532 | int args = 0; | ||
533 | lua_pushinteger(L_, n); // L_: ... finalizers lane_error n | ||
534 | lua_rawget(L_, _finalizers_index); // L_: ... finalizers lane_error finalizer | ||
535 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
536 | if (lua_rc_ != LUA_OK) { // we have an error message and an optional stack trace at the bottom of the stack | ||
537 | LUA_ASSERT(L_, _finalizers_index == 2 || _finalizers_index == 3); | ||
538 | // char const* err_msg = lua_tostring(L_, 1); | ||
539 | lua_pushvalue(L_, 1); // L_: ... finalizers lane_error finalizer err_msg | ||
540 | // note we don't always have a stack trace for example when kCancelError, or when we got an error that doesn't call our handler, such as LUA_ERRMEM | ||
541 | if (_finalizers_index == 3) { | ||
542 | lua_pushvalue(L_, 2); // L_: ... finalizers lane_error finalizer err_msg stack_trace | ||
543 | } | ||
544 | args = _finalizers_index - 1; | ||
545 | } | ||
546 | |||
547 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace | ||
548 | rc = lua_pcall(L_, args, 0, _err_handler_index); // L_: ... finalizers lane_error err_msg2? | ||
549 | if (rc != LUA_OK) { | ||
550 | push_stack_trace(L_, rc, lua_gettop(L_)); // L_: ... finalizers lane_error err_msg2? trace | ||
551 | // If one finalizer fails, don't run the others. Return this | ||
552 | // as the 'real' error, replacing what we could have had (or not) | ||
553 | // from the actual code. | ||
554 | break; | ||
555 | } | ||
556 | // no error, proceed to next finalizer // L_: ... finalizers lane_error | ||
557 | } | ||
558 | |||
559 | if (rc != LUA_OK) { | ||
560 | // ERROR_FULL_STACK accounts for the presence of lane_error on the stack | ||
561 | int const nb_err_slots{ lua_gettop(L_) - _finalizers_index - ERROR_FULL_STACK }; | ||
562 | // a finalizer generated an error, this is what we leave of the stack | ||
563 | for (int n = nb_err_slots; n > 0; --n) { | ||
564 | lua_replace(L_, n); | ||
565 | } | ||
566 | // leave on the stack only the error and optional stack trace produced by the error in the finalizer | ||
567 | lua_settop(L_, nb_err_slots); // L_: ... lane_error trace | ||
568 | } else { // no error from the finalizers, make sure only the original return values from the lane body remain on the stack | ||
569 | lua_settop(L_, _finalizers_index - 1); | ||
570 | } | ||
571 | |||
572 | return rc; | ||
573 | } | ||
574 | |||
575 | // ################################################################################################# | ||
576 | |||
577 | /* | ||
578 | * Add the lane to selfdestruct chain; the ones still running at the end of the | ||
579 | * whole process will be cancelled. | ||
580 | */ | ||
581 | static void selfdestruct_add(Lane* lane_) | ||
582 | { | ||
583 | std::lock_guard<std::mutex> _guard{ lane_->U->selfdestructMutex }; | ||
584 | assert(lane_->selfdestruct_next == nullptr); | ||
585 | |||
586 | lane_->selfdestruct_next = lane_->U->selfdestructFirst; | ||
587 | lane_->U->selfdestructFirst = lane_; | ||
588 | } | ||
589 | |||
590 | // ################################################################################################# | ||
591 | |||
592 | // A free-running lane has ended; remove it from selfdestruct chain | ||
593 | [[nodiscard]] static bool selfdestruct_remove(Lane* lane_) | ||
594 | { | ||
595 | bool _found{ false }; | ||
596 | std::lock_guard<std::mutex> _guard{ lane_->U->selfdestructMutex }; | ||
597 | // Make sure (within the MUTEX) that we actually are in the chain | ||
598 | // still (at process exit they will remove us from chain and then | ||
599 | // cancel/kill). | ||
600 | // | ||
601 | if (lane_->selfdestruct_next != nullptr) { | ||
602 | Lane* volatile* _ref = static_cast<Lane* volatile*>(&lane_->U->selfdestructFirst); | ||
603 | |||
604 | while (*_ref != SELFDESTRUCT_END) { | ||
605 | if (*_ref == lane_) { | ||
606 | *_ref = lane_->selfdestruct_next; | ||
607 | lane_->selfdestruct_next = nullptr; | ||
608 | // the terminal shutdown should wait until the lane is done with its lua_close() | ||
609 | lane_->U->selfdestructingCount.fetch_add(1, std::memory_order_release); | ||
610 | _found = true; | ||
611 | break; | ||
612 | } | ||
613 | _ref = static_cast<Lane* volatile*>(&((*_ref)->selfdestruct_next)); | ||
614 | } | ||
615 | assert(_found); | ||
616 | } | ||
617 | return _found; | ||
618 | } | ||
619 | |||
620 | // ################################################################################################# | ||
621 | // ########################################## Main ################################################# | ||
622 | // ################################################################################################# | ||
623 | |||
624 | static void lane_main(Lane* lane_) | ||
625 | { | ||
626 | lua_State* const _L{ lane_->L }; | ||
627 | // wait until the launching thread has finished preparing L | ||
628 | lane_->ready.wait(); | ||
629 | int _rc{ LUA_ERRRUN }; | ||
630 | if (lane_->status == Lane::Pending) { // nothing wrong happened during preparation, we can work | ||
631 | // At this point, the lane function and arguments are on the stack | ||
632 | int const nargs{ lua_gettop(_L) - 1 }; | ||
633 | DEBUGSPEW_CODE(Universe* U = universe_get(_L)); | ||
634 | lane_->status = Lane::Running; // Pending -> Running | ||
635 | |||
636 | // Tie "set_finalizer()" to the state | ||
637 | lua_pushcfunction(_L, LG_set_finalizer); | ||
638 | populate_func_lookup_table(_L, -1, "set_finalizer"); | ||
639 | lua_setglobal(_L, "set_finalizer"); | ||
640 | |||
641 | // Tie "set_debug_threadname()" to the state | ||
642 | // But don't register it in the lookup database because of the Lane pointer upvalue | ||
643 | lua_pushlightuserdata(_L, lane_); | ||
644 | lua_pushcclosure(_L, LG_set_debug_threadname, 1); | ||
645 | lua_setglobal(_L, "set_debug_threadname"); | ||
646 | |||
647 | // Tie "cancel_test()" to the state | ||
648 | lua_pushcfunction(_L, LG_cancel_test); | ||
649 | populate_func_lookup_table(_L, -1, "cancel_test"); | ||
650 | lua_setglobal(_L, "cancel_test"); | ||
651 | |||
652 | // this could be done in lane_new before the lane body function is pushed on the stack to avoid unnecessary stack slot shifting around | ||
653 | #if ERROR_FULL_STACK | ||
654 | // Tie "set_error_reporting()" to the state | ||
655 | lua_pushcfunction(_L, LG_set_error_reporting); | ||
656 | populate_func_lookup_table(_L, -1, "set_error_reporting"); | ||
657 | lua_setglobal(_L, "set_error_reporting"); | ||
658 | |||
659 | STACK_GROW(_L, 1); | ||
660 | lua_pushcfunction(_L, lane_error); // L: func args handler | ||
661 | lua_insert(_L, 1); // L: handler func args | ||
662 | #endif // L: ERROR_FULL_STACK | ||
663 | |||
664 | _rc = lua_pcall(_L, nargs, LUA_MULTRET, ERROR_FULL_STACK); // L: retvals|err | ||
665 | |||
666 | #if ERROR_FULL_STACK | ||
667 | lua_remove(_L, 1); // L: retvals|error | ||
668 | #endif // ERROR_FULL_STACK | ||
669 | |||
670 | // in case of error and if it exists, fetch stack trace from registry and push it | ||
671 | push_stack_trace(_L, _rc, 1); // L: retvals|error [trace] | ||
672 | |||
673 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p body: %s (%s)\n" INDENT_END(U), _L, get_errcode_name(_rc), kCancelError.equals(_L, 1) ? "cancelled" : lua_typename(_L, lua_type(_L, 1)))); | ||
674 | // Call finalizers, if the script has set them up. | ||
675 | // | ||
676 | int _rc2{ run_finalizers(_L, _rc) }; | ||
677 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p finalizer: %s\n" INDENT_END(U), _L, get_errcode_name(_rc2))); | ||
678 | if (_rc2 != LUA_OK) { // Error within a finalizer! | ||
679 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack | ||
680 | _rc = _rc2; // we're overruling the earlier script error or normal return | ||
681 | } | ||
682 | lane_->waiting_on = nullptr; // just in case | ||
683 | if (selfdestruct_remove(lane_)) { // check and remove (under lock!) | ||
684 | // We're a free-running thread and no-one's there to clean us up. | ||
685 | lua_close(lane_->L); | ||
686 | lane_->L = nullptr; // just in case | ||
687 | lane_->U->selfdestructMutex.lock(); | ||
688 | // done with lua_close(), terminal shutdown sequence may proceed | ||
689 | lane_->U->selfdestructingCount.fetch_sub(1, std::memory_order_release); | ||
690 | lane_->U->selfdestructMutex.unlock(); | ||
691 | |||
692 | // we destroy our jthread member from inside the thread body, so we have to detach so that we don't try to join, as this doesn't seem a good idea | ||
693 | lane_->thread.detach(); | ||
694 | delete lane_; | ||
695 | lane_ = nullptr; | ||
696 | } | ||
697 | } | ||
698 | if (lane_) { | ||
699 | // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them | ||
700 | |||
701 | Lane::Status const _st = (_rc == LUA_OK) ? Lane::Done : kCancelError.equals(_L, 1) ? Lane::Cancelled : Lane::Error; | ||
702 | |||
703 | { | ||
704 | // 'doneMutex' protects the -> Done|Error|Cancelled state change | ||
705 | std::lock_guard lock{ lane_->doneMutex }; | ||
706 | lane_->status = _st; | ||
707 | lane_->doneCondVar.notify_one(); // wake up master (while 'lane_->doneMutex' is on) | ||
708 | } | ||
709 | } | ||
710 | } | ||
711 | |||
712 | // ################################################################################################# | ||
713 | |||
714 | // = thread_gc( lane_ud ) | ||
715 | // | ||
716 | // Cleanup for a thread userdata. If the thread is still executing, leave it | ||
717 | // alive as a free-running thread (will clean up itself). | ||
718 | // | ||
719 | // * Why NOT cancel/kill a loose thread: | ||
720 | // | ||
721 | // At least timer system uses a free-running thread, they should be handy | ||
722 | // and the issue of canceling/killing threads at gc is not very nice, either | ||
723 | // (would easily cause waits at gc cycle, which we don't want). | ||
724 | // | ||
725 | [[nodiscard]] static int lane_gc(lua_State* L_) | ||
726 | { | ||
727 | bool _have_gc_cb{ false }; | ||
728 | Lane* const _lane{ ToLane(L_, 1) }; // L_: ud | ||
729 | |||
730 | // if there a gc callback? | ||
731 | lua_getiuservalue(L_, 1, 1); // L_: ud uservalue | ||
732 | kLaneGC.pushKey(L_); // L_: ud uservalue __gc | ||
733 | lua_rawget(L_, -2); // L_: ud uservalue gc_cb|nil | ||
734 | if (!lua_isnil(L_, -1)) { | ||
735 | lua_remove(L_, -2); // L_: ud gc_cb|nil | ||
736 | lua_pushstring(L_, _lane->debugName); // L_: ud gc_cb name | ||
737 | _have_gc_cb = true; | ||
738 | } else { | ||
739 | lua_pop(L_, 2); // L_: ud | ||
740 | } | ||
741 | |||
742 | // We can read 'lane->status' without locks, but not wait for it | ||
743 | if (_lane->status < Lane::Done) { | ||
744 | // still running: will have to be cleaned up later | ||
745 | selfdestruct_add(_lane); | ||
746 | assert(_lane->selfdestruct_next); | ||
747 | if (_have_gc_cb) { | ||
748 | lua_pushliteral(L_, "selfdestruct"); // L_: ud gc_cb name status | ||
749 | lua_call(L_, 2, 0); // L_: ud | ||
750 | } | ||
751 | return 0; | ||
752 | } else if (_lane->L) { | ||
753 | // no longer accessing the Lua VM: we can close right now | ||
754 | lua_close(_lane->L); | ||
755 | _lane->L = nullptr; | ||
756 | // just in case, but s will be freed soon so... | ||
757 | _lane->debugName = "<gc>"; | ||
758 | } | ||
759 | |||
760 | // Clean up after a (finished) thread | ||
761 | delete _lane; | ||
762 | |||
763 | // do this after lane cleanup in case the callback triggers an error | ||
764 | if (_have_gc_cb) { | ||
765 | lua_pushliteral(L_, "closed"); // L_: ud gc_cb name status | ||
766 | lua_call(L_, 2, 0); // L_: ud | ||
767 | } | ||
768 | return 0; | ||
769 | } | ||
770 | |||
771 | // ################################################################################################# | ||
772 | // #################################### Lane implementation ######################################## | ||
773 | // ################################################################################################# | ||
774 | |||
775 | Lane::Lane(Universe* U_, lua_State* L_) | ||
776 | : U{ U_ } | ||
777 | , L{ L_ } | ||
778 | { | ||
779 | #if HAVE_LANE_TRACKING() | ||
780 | U->tracker.tracking_add(this); | ||
781 | #endif // HAVE_LANE_TRACKING() | ||
782 | } | ||
783 | |||
784 | // ################################################################################################# | ||
785 | |||
786 | Lane::~Lane() | ||
787 | { | ||
788 | // Clean up after a (finished) thread | ||
789 | // | ||
790 | #if HAVE_LANE_TRACKING() | ||
791 | std::ignore = U->tracker.tracking_remove(this); | ||
792 | #endif // HAVE_LANE_TRACKING() | ||
793 | } | ||
794 | |||
795 | // ################################################################################################# | ||
796 | |||
797 | void Lane::changeDebugName(int nameIdx_) | ||
798 | { | ||
799 | // xxh64 of string "debugName" generated at https://www.pelock.com/products/hash-calculator | ||
800 | static constexpr RegistryUniqueKey kRegKey{ 0xA194E2645C57F6DDull }; | ||
801 | nameIdx_ = lua_absindex(L, nameIdx_); | ||
802 | luaL_checktype(L, nameIdx_, LUA_TSTRING); // L: ... "name" ... | ||
803 | STACK_CHECK_START_REL(L, 0); | ||
804 | // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global... | ||
805 | kRegKey.setValue(L, [nameIdx = nameIdx_](lua_State* L_) { lua_pushvalue(L_, nameIdx); }); // L: ... "name" ... | ||
806 | // keep a direct pointer on the string | ||
807 | debugName = lua_tostring(L, nameIdx_); | ||
808 | // to see VM name in Decoda debugger Virtual Machine window | ||
809 | lua_pushvalue(L, nameIdx_); // L: ... "name" ... "name" | ||
810 | lua_setglobal(L, "decoda_name"); // L: ... "name" ... | ||
811 | // and finally set the OS thread name | ||
812 | THREAD_SETNAME(debugName); | ||
813 | STACK_CHECK(L, 0); | ||
814 | } | ||
815 | |||
816 | // ################################################################################################# | ||
817 | |||
818 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } | ||
819 | void Lane::PushMetatable(lua_State* L_) | ||
820 | { | ||
821 | if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: mt | ||
822 | lua_pushcfunction(L_, lane_gc); // L_: mt lane_gc | ||
823 | lua_setfield(L_, -2, "__gc"); // L_: mt | ||
824 | lua_pushcfunction(L_, LG_thread_index); // L_: mt LG_thread_index | ||
825 | lua_setfield(L_, -2, "__index"); // L_: mt | ||
826 | lua_getglobal(L_, "error"); // L_: mt error | ||
827 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
828 | lua_setfield(L_, -2, "cached_error"); // L_: mt | ||
829 | lua_getglobal(L_, "tostring"); // L_: mt tostring | ||
830 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
831 | lua_setfield(L_, -2, "cached_tostring"); // L_: mt | ||
832 | lua_pushcfunction(L_, LG_thread_join); // L_: mt LG_thread_join | ||
833 | lua_setfield(L_, -2, "join"); // L_: mt | ||
834 | lua_pushcfunction(L_, LG_get_debug_threadname); // L_: mt LG_get_debug_threadname | ||
835 | lua_setfield(L_, -2, "get_debug_threadname"); // L_: mt | ||
836 | lua_pushcfunction(L_, LG_thread_cancel); // L_: mt LG_thread_cancel | ||
837 | lua_setfield(L_, -2, "cancel"); // L_: mt | ||
838 | lua_pushliteral(L_, kLaneMetatableName); // L_: mt "Lane" | ||
839 | lua_setfield(L_, -2, "__metatable"); // L_: mt | ||
840 | } | ||
841 | } | ||
842 | // ################################################################################################# | ||
843 | |||
844 | void Lane::pushThreadStatus(lua_State* L_) const | ||
845 | { | ||
846 | char const* const _str{ threadStatusString() }; | ||
847 | LUA_ASSERT(L_, _str); | ||
848 | |||
849 | lua_pushstring(L_, _str); | ||
850 | } | ||
851 | |||
852 | // ################################################################################################# | ||
853 | |||
854 | // intern the debug name in the caller lua state so that the pointer remains valid after the lane's state is closed | ||
855 | void Lane::securizeDebugName(lua_State* L_) | ||
856 | { | ||
857 | STACK_CHECK_START_REL(L_, 0); | ||
858 | STACK_GROW(L_, 3); | ||
859 | // a Lane's uservalue should be a table | ||
860 | lua_getiuservalue(L_, 1, 1); // L_: lane ... {uv} | ||
861 | LUA_ASSERT(L_, lua_istable(L_, -1)); | ||
862 | // we don't care about the actual key, so long as it's unique and can't collide with anything. | ||
863 | lua_newtable(L_); // L_: lane ... {uv} {} | ||
864 | // Lua 5.1 can't do 'lane_->debugName = lua_pushstring(L_, lane_->debugName);' | ||
865 | lua_pushstring(L_, debugName); // L_: lane ... {uv} {} name | ||
866 | debugName = lua_tostring(L_, -1); | ||
867 | lua_rawset(L_, -3); // L_: lane ... {uv} | ||
868 | lua_pop(L_, 1); // L_: lane | ||
869 | STACK_CHECK(L_, 0); | ||
870 | } | ||
871 | |||
872 | // ################################################################################################# | ||
873 | |||
874 | void Lane::startThread(int priority_) | ||
875 | { | ||
876 | thread = std::jthread([this]() { lane_main(this); }); | ||
877 | if (priority_ != kThreadPrioDefault) { | ||
878 | JTHREAD_SET_PRIORITY(thread, priority_, U->sudo); | ||
879 | } | ||
880 | } | ||
881 | |||
882 | // ################################################################################################# | ||
883 | |||
884 | //--- | ||
885 | // str= thread_status( lane ) | ||
886 | // | ||
887 | // Returns: "pending" not started yet | ||
888 | // -> "running" started, doing its work.. | ||
889 | // <-> "waiting" blocked in a receive() | ||
890 | // -> "done" finished, results are there | ||
891 | // / "error" finished at an error, error value is there | ||
892 | // / "cancelled" execution cancelled by M (state gone) | ||
893 | // | ||
894 | [[nodiscard]] char const* Lane::threadStatusString() const | ||
895 | { | ||
896 | char const* const _str{ | ||
897 | (status == Lane::Pending) ? "pending" : | ||
898 | (status == Lane::Running) ? "running" : // like in 'co.status()' | ||
899 | (status == Lane::Waiting) ? "waiting" : | ||
900 | (status == Lane::Done) ? "done" : | ||
901 | (status == Lane::Error) ? "error" : | ||
902 | (status == Lane::Cancelled) ? "cancelled" : | ||
903 | nullptr | ||
904 | }; | ||
905 | return _str; | ||
906 | } | ||
907 | |||
908 | // ################################################################################################# | ||
909 | |||
910 | bool Lane::waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_) | ||
911 | { | ||
912 | std::unique_lock _guard{ doneMutex }; | ||
913 | // std::stop_token token{ thread.get_stop_token() }; | ||
914 | // return doneCondVar.wait_until(lock, token, secs_, [this](){ return status >= Lane::Done; }); | ||
915 | return doneCondVar.wait_until(_guard, until_, [this]() { return status >= Lane::Done; }); | ||
916 | } | ||
diff --git a/src/lanes_private.h b/src/lane.h index a756c42..f28a402 100644 --- a/src/lanes_private.h +++ b/src/lane.h | |||
@@ -10,6 +10,30 @@ | |||
10 | #include <stop_token> | 10 | #include <stop_token> |
11 | #include <thread> | 11 | #include <thread> |
12 | 12 | ||
13 | // ################################################################################################# | ||
14 | |||
15 | /* | ||
16 | * registry[FINALIZER_REG_KEY] is either nil (no finalizers) or a table | ||
17 | * of functions that Lanes will call after the executing 'pcall' has ended. | ||
18 | * | ||
19 | * We're NOT using the GC system for finalizer mainly because providing the | ||
20 | * error (and maybe stack trace) parameters to the finalizer functions would | ||
21 | * anyways complicate that approach. | ||
22 | */ | ||
23 | // xxh64 of string "kFinalizerRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
24 | static constexpr RegistryUniqueKey kFinalizerRegKey{ 0xFE936BFAA718FEEAull }; | ||
25 | |||
26 | // xxh64 of string "kExtendedStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
27 | static constexpr RegistryUniqueKey kExtendedStackTraceRegKey{ 0x38147AD48FB426E2ull }; // used as registry key | ||
28 | |||
29 | // xxh64 of string "kLaneGC" generated at https://www.pelock.com/products/hash-calculator | ||
30 | static constexpr UniqueKey kLaneGC{ 0x5D6122141727F960ull }; | ||
31 | |||
32 | // xxh64 of string "kLanePointerRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
33 | static constexpr RegistryUniqueKey kLanePointerRegKey{ 0x2D8CF03FE9F0A51Aull }; // used as registry key | ||
34 | |||
35 | // ################################################################################################# | ||
36 | |||
13 | // The chain is ended by '(Lane*)(-1)', not nullptr: 'selfdestructFirst -> ... -> ... -> (-1)' | 37 | // The chain is ended by '(Lane*)(-1)', not nullptr: 'selfdestructFirst -> ... -> ... -> (-1)' |
14 | #define SELFDESTRUCT_END ((Lane*) (-1)) | 38 | #define SELFDESTRUCT_END ((Lane*) (-1)) |
15 | 39 | ||
@@ -94,15 +118,17 @@ class Lane | |||
94 | Lane(Universe* U_, lua_State* L_); | 118 | Lane(Universe* U_, lua_State* L_); |
95 | ~Lane(); | 119 | ~Lane(); |
96 | 120 | ||
97 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); | ||
98 | void startThread(int priority_); | ||
99 | void pushThreadStatus(lua_State* L_); | ||
100 | void changeDebugName(int nameIdx_); | 121 | void changeDebugName(int nameIdx_); |
122 | void pushThreadStatus(lua_State* L_) const; | ||
101 | void securizeDebugName(lua_State* L_); | 123 | void securizeDebugName(lua_State* L_); |
124 | void startThread(int priority_); | ||
125 | char const* threadStatusString() const; | ||
126 | [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_); | ||
127 | |||
128 | static void PushMetatable(lua_State* L_); | ||
102 | }; | 129 | }; |
103 | 130 | ||
104 | // xxh64 of string "kLanePointerRegKey" generated at https://www.pelock.com/products/hash-calculator | 131 | // ################################################################################################# |
105 | static constexpr RegistryUniqueKey kLanePointerRegKey{ 0x2D8CF03FE9F0A51Aull }; // used as registry key | ||
106 | 132 | ||
107 | // To allow free-running threads (longer lifespan than the handle's) | 133 | // To allow free-running threads (longer lifespan than the handle's) |
108 | // 'Lane' are malloc/free'd and the handle only carries a pointer. | 134 | // 'Lane' are malloc/free'd and the handle only carries a pointer. |
diff --git a/src/lanes.cpp b/src/lanes.cpp index f8efa42..a775895 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
@@ -84,11 +84,10 @@ THE SOFTWARE. | |||
84 | #include "deep.h" | 84 | #include "deep.h" |
85 | #include "intercopycontext.h" | 85 | #include "intercopycontext.h" |
86 | #include "keeper.h" | 86 | #include "keeper.h" |
87 | #include "lanes_private.h" | 87 | #include "lane.h" |
88 | #include "state.h" | 88 | #include "state.h" |
89 | #include "threading.h" | 89 | #include "threading.h" |
90 | #include "tools.h" | 90 | #include "tools.h" |
91 | #include "universe.h" | ||
92 | 91 | ||
93 | #if !(defined(PLATFORM_XBOX) || defined(PLATFORM_WIN32) || defined(PLATFORM_POCKETPC)) | 92 | #if !(defined(PLATFORM_XBOX) || defined(PLATFORM_WIN32) || defined(PLATFORM_POCKETPC)) |
94 | #include <sys/time.h> | 93 | #include <sys/time.h> |
@@ -103,279 +102,9 @@ THE SOFTWARE. | |||
103 | #include <atomic> | 102 | #include <atomic> |
104 | 103 | ||
105 | // ################################################################################################# | 104 | // ################################################################################################# |
106 | |||
107 | Lane::Lane(Universe* U_, lua_State* L_) | ||
108 | : U{ U_ } | ||
109 | , L{ L_ } | ||
110 | { | ||
111 | #if HAVE_LANE_TRACKING() | ||
112 | U->tracker.tracking_add(this); | ||
113 | #endif // HAVE_LANE_TRACKING() | ||
114 | } | ||
115 | |||
116 | // ################################################################################################# | ||
117 | |||
118 | bool Lane::waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_) | ||
119 | { | ||
120 | std::unique_lock _guard{ doneMutex }; | ||
121 | // std::stop_token token{ thread.get_stop_token() }; | ||
122 | // return doneCondVar.wait_until(lock, token, secs_, [this](){ return status >= Lane::Done; }); | ||
123 | return doneCondVar.wait_until(_guard, until_, [this]() { return status >= Lane::Done; }); | ||
124 | } | ||
125 | |||
126 | // ################################################################################################# | ||
127 | |||
128 | static void lane_main(Lane* lane_); | ||
129 | void Lane::startThread(int priority_) | ||
130 | { | ||
131 | thread = std::jthread([this]() { lane_main(this); }); | ||
132 | if (priority_ != kThreadPrioDefault) { | ||
133 | JTHREAD_SET_PRIORITY(thread, priority_, U->sudo); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | // ################################################################################################# | ||
138 | |||
139 | /* Do you want full call stacks, or just the line where the error happened? | ||
140 | * | ||
141 | * TBD: The full stack feature does not seem to work (try 'make error'). | ||
142 | */ | ||
143 | #define ERROR_FULL_STACK 1 // must be either 0 or 1 as we do some index arithmetics with it! | ||
144 | |||
145 | // intern the debug name in the caller lua state so that the pointer remains valid after the lane's state is closed | ||
146 | void Lane::securizeDebugName(lua_State* L_) | ||
147 | { | ||
148 | STACK_CHECK_START_REL(L_, 0); | ||
149 | STACK_GROW(L_, 3); | ||
150 | // a Lane's uservalue should be a table | ||
151 | lua_getiuservalue(L_, 1, 1); // L_: lane ... {uv} | ||
152 | LUA_ASSERT(L_, lua_istable(L_, -1)); | ||
153 | // we don't care about the actual key, so long as it's unique and can't collide with anything. | ||
154 | lua_newtable(L_); // L_: lane ... {uv} {} | ||
155 | // Lua 5.1 can't do 'lane_->debugName = lua_pushstring(L_, lane_->debugName);' | ||
156 | lua_pushstring(L_, debugName); // L_: lane ... {uv} {} name | ||
157 | debugName = lua_tostring(L_, -1); | ||
158 | lua_rawset(L_, -3); // L_: lane ... {uv} | ||
159 | lua_pop(L_, 1); // L_: lane | ||
160 | STACK_CHECK(L_, 0); | ||
161 | } | ||
162 | |||
163 | // ################################################################################################# | ||
164 | |||
165 | #if ERROR_FULL_STACK | ||
166 | [[nodiscard]] static int lane_error(lua_State* L_); | ||
167 | // xxh64 of string "kStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
168 | static constexpr RegistryUniqueKey kStackTraceRegKey{ 0x3F327747CACAA904ull }; | ||
169 | #endif // ERROR_FULL_STACK | ||
170 | |||
171 | /* | ||
172 | * registry[FINALIZER_REG_KEY] is either nil (no finalizers) or a table | ||
173 | * of functions that Lanes will call after the executing 'pcall' has ended. | ||
174 | * | ||
175 | * We're NOT using the GC system for finalizer mainly because providing the | ||
176 | * error (and maybe stack trace) parameters to the finalizer functions would | ||
177 | * anyways complicate that approach. | ||
178 | */ | ||
179 | // xxh64 of string "kFinalizerRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
180 | static constexpr RegistryUniqueKey kFinalizerRegKey{ 0xFE936BFAA718FEEAull }; | ||
181 | |||
182 | // ################################################################################################# | ||
183 | |||
184 | Lane::~Lane() | ||
185 | { | ||
186 | // Clean up after a (finished) thread | ||
187 | // | ||
188 | #if HAVE_LANE_TRACKING() | ||
189 | std::ignore = U->tracker.tracking_remove(this); | ||
190 | #endif // HAVE_LANE_TRACKING() | ||
191 | } | ||
192 | |||
193 | // ################################################################################################# | ||
194 | // ########################################## Finalizer ############################################ | ||
195 | // ################################################################################################# | ||
196 | |||
197 | // void= finalizer( finalizer_func ) | ||
198 | // | ||
199 | // finalizer_func( [err, stack_tbl] ) | ||
200 | // | ||
201 | // Add a function that will be called when exiting the lane, either via | ||
202 | // normal return or an error. | ||
203 | // | ||
204 | LUAG_FUNC(set_finalizer) | ||
205 | { | ||
206 | luaL_argcheck(L_, lua_isfunction(L_, 1), 1, "finalizer should be a function"); | ||
207 | luaL_argcheck(L_, lua_gettop(L_) == 1, 1, "too many arguments"); | ||
208 | STACK_GROW(L_, 3); | ||
209 | // Get the current finalizer table (if any), create one if it doesn't exist | ||
210 | std::ignore = kFinalizerRegKey.getSubTable(L_, 1, 0); // L_: finalizer {finalisers} | ||
211 | // must cast to int, not lua_Integer, because LuaJIT signature of lua_rawseti is not the same as PUC-Lua. | ||
212 | int const _idx{ static_cast<int>(lua_rawlen(L_, -1) + 1) }; | ||
213 | lua_pushvalue(L_, 1); // L_: finalizer {finalisers} finalizer | ||
214 | lua_rawseti(L_, -2, _idx); // L_: finalizer {finalisers} | ||
215 | // no need to adjust the stack, Lua does this for us | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | // ################################################################################################# | ||
220 | |||
221 | static void push_stack_trace(lua_State* L_, int rc_, int stk_base_) | ||
222 | { | ||
223 | // Lua 5.1 error handler is limited to one return value; it stored the stack trace in the registry | ||
224 | switch (rc_) { | ||
225 | case LUA_OK: // no error, body return values are on the stack | ||
226 | break; | ||
227 | |||
228 | case LUA_ERRRUN: // cancellation or a runtime error | ||
229 | #if ERROR_FULL_STACK // when ERROR_FULL_STACK, we installed a handler | ||
230 | { | ||
231 | STACK_CHECK_START_REL(L_, 0); | ||
232 | // fetch the call stack table from the registry where the handler stored it | ||
233 | STACK_GROW(L_, 1); | ||
234 | // yields nil if no stack was generated (in case of cancellation for example) | ||
235 | kStackTraceRegKey.pushValue(L_); // L_: err trace|nil | ||
236 | STACK_CHECK(L_, 1); | ||
237 | |||
238 | // For cancellation the error message is kCancelError, and a stack trace isn't placed | ||
239 | // For other errors, the message can be whatever was thrown, and we should have a stack trace table | ||
240 | LUA_ASSERT(L_, lua_type(L_, 1 + stk_base_) == (kCancelError.equals(L_, stk_base_) ? LUA_TNIL : LUA_TTABLE)); | ||
241 | // Just leaving the stack trace table on the stack is enough to get it through to the master. | ||
242 | break; | ||
243 | } | ||
244 | #else // !ERROR_FULL_STACK | ||
245 | [[fallthrough]]; // fall through if not ERROR_FULL_STACK | ||
246 | #endif // !ERROR_FULL_STACK | ||
247 | |||
248 | case LUA_ERRMEM: // memory allocation error (handler not called) | ||
249 | case LUA_ERRERR: // error while running the error handler (if any, for example an out-of-memory condition) | ||
250 | default: | ||
251 | // we should have a single value which is either a string (the error message) or kCancelError | ||
252 | LUA_ASSERT(L_, (lua_gettop(L_) == stk_base_) && ((lua_type(L_, stk_base_) == LUA_TSTRING) || kCancelError.equals(L_, stk_base_))); | ||
253 | break; | ||
254 | } | ||
255 | } | ||
256 | |||
257 | // ################################################################################################# | ||
258 | //--- | ||
259 | // Run finalizers - if any - with the given parameters | ||
260 | // | ||
261 | // If 'rc' is nonzero, error message and stack index (the latter only when ERROR_FULL_STACK == 1) are available as: | ||
262 | // [-1]: stack trace (table) | ||
263 | // [-2]: error message (any type) | ||
264 | // | ||
265 | // Returns: | ||
266 | // 0 if finalizers were run without error (or there were none) | ||
267 | // LUA_ERRxxx return code if any of the finalizers failed | ||
268 | // | ||
269 | // TBD: should we add stack trace on failing finalizer, wouldn't be hard.. | ||
270 | // | ||
271 | |||
272 | [[nodiscard]] static int run_finalizers(lua_State* L_, int lua_rc_) | ||
273 | { | ||
274 | kFinalizerRegKey.pushValue(L_); // L_: ... finalizers? | ||
275 | if (lua_isnil(L_, -1)) { | ||
276 | lua_pop(L_, 1); | ||
277 | return 0; // no finalizers | ||
278 | } | ||
279 | |||
280 | STACK_GROW(L_, 5); | ||
281 | |||
282 | int const _finalizers_index{ lua_gettop(L_) }; | ||
283 | int const _err_handler_index{ ERROR_FULL_STACK ? (lua_pushcfunction(L_, lane_error), lua_gettop(L_)) : 0 }; | ||
284 | |||
285 | int rc{ LUA_OK }; | ||
286 | for (int n = static_cast<int>(lua_rawlen(L_, _finalizers_index)); n > 0; --n) { | ||
287 | int args = 0; | ||
288 | lua_pushinteger(L_, n); // L_: ... finalizers lane_error n | ||
289 | lua_rawget(L_, _finalizers_index); // L_: ... finalizers lane_error finalizer | ||
290 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
291 | if (lua_rc_ != LUA_OK) { // we have an error message and an optional stack trace at the bottom of the stack | ||
292 | LUA_ASSERT(L_, _finalizers_index == 2 || _finalizers_index == 3); | ||
293 | // char const* err_msg = lua_tostring(L_, 1); | ||
294 | lua_pushvalue(L_, 1); // L_: ... finalizers lane_error finalizer err_msg | ||
295 | // note we don't always have a stack trace for example when kCancelError, or when we got an error that doesn't call our handler, such as LUA_ERRMEM | ||
296 | if (_finalizers_index == 3) { | ||
297 | lua_pushvalue(L_, 2); // L_: ... finalizers lane_error finalizer err_msg stack_trace | ||
298 | } | ||
299 | args = _finalizers_index - 1; | ||
300 | } | ||
301 | |||
302 | // if no error from the main body, finalizer doesn't receive any argument, else it gets the error message and optional stack trace | ||
303 | rc = lua_pcall(L_, args, 0, _err_handler_index); // L_: ... finalizers lane_error err_msg2? | ||
304 | if (rc != LUA_OK) { | ||
305 | push_stack_trace(L_, rc, lua_gettop(L_)); // L_: ... finalizers lane_error err_msg2? trace | ||
306 | // If one finalizer fails, don't run the others. Return this | ||
307 | // as the 'real' error, replacing what we could have had (or not) | ||
308 | // from the actual code. | ||
309 | break; | ||
310 | } | ||
311 | // no error, proceed to next finalizer // L_: ... finalizers lane_error | ||
312 | } | ||
313 | |||
314 | if (rc != LUA_OK) { | ||
315 | // ERROR_FULL_STACK accounts for the presence of lane_error on the stack | ||
316 | int const nb_err_slots{ lua_gettop(L_) - _finalizers_index - ERROR_FULL_STACK }; | ||
317 | // a finalizer generated an error, this is what we leave of the stack | ||
318 | for (int n = nb_err_slots; n > 0; --n) { | ||
319 | lua_replace(L_, n); | ||
320 | } | ||
321 | // leave on the stack only the error and optional stack trace produced by the error in the finalizer | ||
322 | lua_settop(L_, nb_err_slots); // L_: ... lane_error trace | ||
323 | } else { // no error from the finalizers, make sure only the original return values from the lane body remain on the stack | ||
324 | lua_settop(L_, _finalizers_index - 1); | ||
325 | } | ||
326 | |||
327 | return rc; | ||
328 | } | ||
329 | |||
330 | // ################################################################################################# | ||
331 | // ########################################### Threads ############################################# | 105 | // ########################################### Threads ############################################# |
332 | // ################################################################################################# | 106 | // ################################################################################################# |
333 | 107 | ||
334 | /* | ||
335 | * Add the lane to selfdestruct chain; the ones still running at the end of the | ||
336 | * whole process will be cancelled. | ||
337 | */ | ||
338 | static void selfdestruct_add(Lane* lane_) | ||
339 | { | ||
340 | std::lock_guard<std::mutex> _guard{ lane_->U->selfdestructMutex }; | ||
341 | assert(lane_->selfdestruct_next == nullptr); | ||
342 | |||
343 | lane_->selfdestruct_next = lane_->U->selfdestructFirst; | ||
344 | lane_->U->selfdestructFirst = lane_; | ||
345 | } | ||
346 | |||
347 | // ################################################################################################# | ||
348 | |||
349 | // A free-running lane has ended; remove it from selfdestruct chain | ||
350 | [[nodiscard]] static bool selfdestruct_remove(Lane* lane_) | ||
351 | { | ||
352 | bool _found{ false }; | ||
353 | std::lock_guard<std::mutex> _guard{ lane_->U->selfdestructMutex }; | ||
354 | // Make sure (within the MUTEX) that we actually are in the chain | ||
355 | // still (at process exit they will remove us from chain and then | ||
356 | // cancel/kill). | ||
357 | // | ||
358 | if (lane_->selfdestruct_next != nullptr) { | ||
359 | Lane* volatile* _ref = static_cast<Lane* volatile*>(&lane_->U->selfdestructFirst); | ||
360 | |||
361 | while (*_ref != SELFDESTRUCT_END) { | ||
362 | if (*_ref == lane_) { | ||
363 | *_ref = lane_->selfdestruct_next; | ||
364 | lane_->selfdestruct_next = nullptr; | ||
365 | // the terminal shutdown should wait until the lane is done with its lua_close() | ||
366 | lane_->U->selfdestructingCount.fetch_add(1, std::memory_order_release); | ||
367 | _found = true; | ||
368 | break; | ||
369 | } | ||
370 | _ref = static_cast<Lane* volatile*>(&((*_ref)->selfdestruct_next)); | ||
371 | } | ||
372 | assert(_found); | ||
373 | } | ||
374 | return _found; | ||
375 | } | ||
376 | |||
377 | // ################################################################################################# | ||
378 | |||
379 | //--- | 108 | //--- |
380 | // = _single( [cores_uint=1] ) | 109 | // = _single( [cores_uint=1] ) |
381 | // | 110 | // |
@@ -404,159 +133,6 @@ LUAG_FUNC(set_singlethreaded) | |||
404 | 133 | ||
405 | // ################################################################################################# | 134 | // ################################################################################################# |
406 | 135 | ||
407 | /* | ||
408 | * str= lane_error( error_val|str ) | ||
409 | * | ||
410 | * Called if there's an error in some lane; add call stack to error message | ||
411 | * just like 'lua.c' normally does. | ||
412 | * | ||
413 | * ".. will be called with the error message and its return value will be the | ||
414 | * message returned on the stack by lua_pcall." | ||
415 | * | ||
416 | * Note: Rather than modifying the error message itself, it would be better | ||
417 | * to provide the call stack (as string) completely separated. This would | ||
418 | * work great with non-string error values as well (current system does not). | ||
419 | * (This is NOT possible with the Lua 5.1 'lua_pcall()'; we could of course | ||
420 | * implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) | ||
421 | * --AKa 22-Jan-2009 | ||
422 | */ | ||
423 | #if ERROR_FULL_STACK | ||
424 | |||
425 | // xxh64 of string "kExtendedStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
426 | static constexpr RegistryUniqueKey kExtendedStackTraceRegKey{ 0x38147AD48FB426E2ull }; // used as registry key | ||
427 | |||
428 | LUAG_FUNC(set_error_reporting) | ||
429 | { | ||
430 | luaL_checktype(L_, 1, LUA_TSTRING); | ||
431 | char const* _mode{ lua_tostring(L_, 1) }; | ||
432 | lua_pushliteral(L_, "extended"); | ||
433 | bool const _extended{ strcmp(_mode, "extended") == 0 }; | ||
434 | bool const _basic{ strcmp(_mode, "basic") == 0 }; | ||
435 | if (!_extended && !_basic) { | ||
436 | raise_luaL_error(L_, "unsupported error reporting model %s", _mode); | ||
437 | } | ||
438 | |||
439 | kExtendedStackTraceRegKey.setValue(L_, [extended = _extended](lua_State* L_) { lua_pushboolean(L_, extended ? 1 : 0); }); | ||
440 | return 0; | ||
441 | } | ||
442 | |||
443 | // ################################################################################################# | ||
444 | |||
445 | [[nodiscard]] static int lane_error(lua_State* L_) | ||
446 | { | ||
447 | // error message (any type) | ||
448 | STACK_CHECK_START_ABS(L_, 1); // L_: some_error | ||
449 | |||
450 | // Don't do stack survey for cancelled lanes. | ||
451 | // | ||
452 | if (kCancelError.equals(L_, 1)) { | ||
453 | return 1; // just pass on | ||
454 | } | ||
455 | |||
456 | STACK_GROW(L_, 3); | ||
457 | bool const _extended{ kExtendedStackTraceRegKey.readBoolValue(L_) }; | ||
458 | STACK_CHECK(L_, 1); | ||
459 | |||
460 | // Place stack trace at 'registry[kStackTraceRegKey]' for the 'lua_pcall()' | ||
461 | // caller to fetch. This bypasses the Lua 5.1 limitation of only one | ||
462 | // return value from error handler to 'lua_pcall()' caller. | ||
463 | |||
464 | // It's adequate to push stack trace as a table. This gives the receiver | ||
465 | // of the stack best means to format it to their liking. Also, it allows | ||
466 | // us to add more stack info later, if needed. | ||
467 | // | ||
468 | // table of { "sourcefile.lua:<line>", ... } | ||
469 | // | ||
470 | lua_newtable(L_); // L_: some_error {} | ||
471 | |||
472 | // Best to start from level 1, but in some cases it might be a C function | ||
473 | // and we don't get '.currentline' for that. It's okay - just keep level | ||
474 | // and table index growing separate. --AKa 22-Jan-2009 | ||
475 | // | ||
476 | lua_Debug _ar; | ||
477 | for (int _n = 1; lua_getstack(L_, _n, &_ar); ++_n) { | ||
478 | lua_getinfo(L_, _extended ? "Sln" : "Sl", &_ar); | ||
479 | if (_extended) { | ||
480 | lua_newtable(L_); // L_: some_error {} {} | ||
481 | |||
482 | lua_pushstring(L_, _ar.source); // L_: some_error {} {} source | ||
483 | lua_setfield(L_, -2, "source"); // L_: some_error {} {} | ||
484 | |||
485 | lua_pushinteger(L_, _ar.currentline); // L_: some_error {} {} currentline | ||
486 | lua_setfield(L_, -2, "currentline"); // L_: some_error {} {} | ||
487 | |||
488 | lua_pushstring(L_, _ar.name); // L_: some_error {} {} name | ||
489 | lua_setfield(L_, -2, "name"); // L_: some_error {} {} | ||
490 | |||
491 | lua_pushstring(L_, _ar.namewhat); // L_: some_error {} {} namewhat | ||
492 | lua_setfield(L_, -2, "namewhat"); // L_: some_error {} {} | ||
493 | |||
494 | lua_pushstring(L_, _ar.what); // L_: some_error {} {} what | ||
495 | lua_setfield(L_, -2, "what"); // L_: some_error {} {} | ||
496 | } else if (_ar.currentline > 0) { | ||
497 | lua_pushfstring(L_, "%s:%d", _ar.short_src, _ar.currentline); // L_: some_error {} "blah:blah" | ||
498 | } else { | ||
499 | lua_pushfstring(L_, "%s:?", _ar.short_src); // L_: some_error {} "blah" | ||
500 | } | ||
501 | lua_rawseti(L_, -2, static_cast<lua_Integer>(_n)); // L_: some_error {} | ||
502 | } | ||
503 | |||
504 | // store the stack trace table in the registry | ||
505 | kStackTraceRegKey.setValue(L_, [](lua_State* L_) { lua_insert(L_, -2); }); // L_: some_error | ||
506 | |||
507 | STACK_CHECK(L_, 1); | ||
508 | return 1; // the untouched error value | ||
509 | } | ||
510 | #endif // ERROR_FULL_STACK | ||
511 | |||
512 | // ################################################################################################# | ||
513 | |||
514 | void Lane::changeDebugName(int nameIdx_) | ||
515 | { | ||
516 | // xxh64 of string "debugName" generated at https://www.pelock.com/products/hash-calculator | ||
517 | static constexpr RegistryUniqueKey kRegKey{ 0xA194E2645C57F6DDull }; | ||
518 | nameIdx_ = lua_absindex(L, nameIdx_); | ||
519 | luaL_checktype(L, nameIdx_, LUA_TSTRING); // L: ... "name" ... | ||
520 | STACK_CHECK_START_REL(L, 0); | ||
521 | // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global... | ||
522 | kRegKey.setValue(L, [nameIdx = nameIdx_](lua_State* L_) { lua_pushvalue(L_, nameIdx); }); // L: ... "name" ... | ||
523 | // keep a direct pointer on the string | ||
524 | debugName = lua_tostring(L, nameIdx_); | ||
525 | // to see VM name in Decoda debugger Virtual Machine window | ||
526 | lua_pushvalue(L, nameIdx_); // L: ... "name" ... "name" | ||
527 | lua_setglobal(L, "decoda_name"); // L: ... "name" ... | ||
528 | // and finally set the OS thread name | ||
529 | THREAD_SETNAME(debugName); | ||
530 | STACK_CHECK(L, 0); | ||
531 | } | ||
532 | |||
533 | // ################################################################################################# | ||
534 | |||
535 | // upvalue #1 is the lane userdata | ||
536 | LUAG_FUNC(set_debug_threadname) | ||
537 | { | ||
538 | // C s_lane structure is a light userdata upvalue | ||
539 | Lane* const _lane{ lua_tolightuserdata<Lane>(L_, lua_upvalueindex(1)) }; | ||
540 | LUA_ASSERT(L_, L_ == _lane->L); // this function is exported in a lane's state, therefore it is callable only from inside the Lane's state | ||
541 | lua_settop(L_, 1); | ||
542 | STACK_CHECK_START_REL(L_, 0); | ||
543 | _lane->changeDebugName(-1); | ||
544 | STACK_CHECK(L_, 0); | ||
545 | return 0; | ||
546 | } | ||
547 | |||
548 | // ################################################################################################# | ||
549 | |||
550 | LUAG_FUNC(get_debug_threadname) | ||
551 | { | ||
552 | Lane* const _lane{ ToLane(L_, 1) }; | ||
553 | luaL_argcheck(L_, lua_gettop(L_) == 1, 2, "too many arguments"); | ||
554 | lua_pushstring(L_, _lane->debugName); | ||
555 | return 1; | ||
556 | } | ||
557 | |||
558 | // ################################################################################################# | ||
559 | |||
560 | LUAG_FUNC(set_thread_priority) | 136 | LUAG_FUNC(set_thread_priority) |
561 | { | 137 | { |
562 | lua_Integer const _prio{ luaL_checkinteger(L_, 1) }; | 138 | lua_Integer const _prio{ luaL_checkinteger(L_, 1) }; |
@@ -584,127 +160,6 @@ LUAG_FUNC(set_thread_affinity) | |||
584 | 160 | ||
585 | // ################################################################################################# | 161 | // ################################################################################################# |
586 | 162 | ||
587 | #if USE_DEBUG_SPEW() | ||
588 | // can't use direct LUA_x errcode indexing because the sequence is not the same between Lua 5.1 and 5.2 :-( | ||
589 | // LUA_ERRERR doesn't have the same value | ||
590 | struct errcode_name | ||
591 | { | ||
592 | int code; | ||
593 | char const* name; | ||
594 | }; | ||
595 | |||
596 | static struct errcode_name s_errcodes[] = { | ||
597 | { LUA_OK, "LUA_OK" }, | ||
598 | { LUA_YIELD, "LUA_YIELD" }, | ||
599 | { LUA_ERRRUN, "LUA_ERRRUN" }, | ||
600 | { LUA_ERRSYNTAX, "LUA_ERRSYNTAX" }, | ||
601 | { LUA_ERRMEM, "LUA_ERRMEM" }, | ||
602 | { LUA_ERRGCMM, "LUA_ERRGCMM" }, | ||
603 | { LUA_ERRERR, "LUA_ERRERR" }, | ||
604 | }; | ||
605 | static char const* get_errcode_name(int _code) | ||
606 | { | ||
607 | for (errcode_name const& _entry : s_errcodes) { | ||
608 | if (_entry.code == _code) { | ||
609 | return _entry.name; | ||
610 | } | ||
611 | } | ||
612 | return "<nullptr>"; | ||
613 | } | ||
614 | #endif // USE_DEBUG_SPEW() | ||
615 | |||
616 | // ################################################################################################# | ||
617 | |||
618 | static void lane_main(Lane* lane_) | ||
619 | { | ||
620 | lua_State* const _L{ lane_->L }; | ||
621 | // wait until the launching thread has finished preparing L | ||
622 | lane_->ready.wait(); | ||
623 | int _rc{ LUA_ERRRUN }; | ||
624 | if (lane_->status == Lane::Pending) { // nothing wrong happened during preparation, we can work | ||
625 | // At this point, the lane function and arguments are on the stack | ||
626 | int const nargs{ lua_gettop(_L) - 1 }; | ||
627 | DEBUGSPEW_CODE(Universe* U = universe_get(_L)); | ||
628 | lane_->status = Lane::Running; // Pending -> Running | ||
629 | |||
630 | // Tie "set_finalizer()" to the state | ||
631 | lua_pushcfunction(_L, LG_set_finalizer); | ||
632 | populate_func_lookup_table(_L, -1, "set_finalizer"); | ||
633 | lua_setglobal(_L, "set_finalizer"); | ||
634 | |||
635 | // Tie "set_debug_threadname()" to the state | ||
636 | // But don't register it in the lookup database because of the Lane pointer upvalue | ||
637 | lua_pushlightuserdata(_L, lane_); | ||
638 | lua_pushcclosure(_L, LG_set_debug_threadname, 1); | ||
639 | lua_setglobal(_L, "set_debug_threadname"); | ||
640 | |||
641 | // Tie "cancel_test()" to the state | ||
642 | lua_pushcfunction(_L, LG_cancel_test); | ||
643 | populate_func_lookup_table(_L, -1, "cancel_test"); | ||
644 | lua_setglobal(_L, "cancel_test"); | ||
645 | |||
646 | // this could be done in lane_new before the lane body function is pushed on the stack to avoid unnecessary stack slot shifting around | ||
647 | #if ERROR_FULL_STACK | ||
648 | // Tie "set_error_reporting()" to the state | ||
649 | lua_pushcfunction(_L, LG_set_error_reporting); | ||
650 | populate_func_lookup_table(_L, -1, "set_error_reporting"); | ||
651 | lua_setglobal(_L, "set_error_reporting"); | ||
652 | |||
653 | STACK_GROW(_L, 1); | ||
654 | lua_pushcfunction(_L, lane_error); // L: func args handler | ||
655 | lua_insert(_L, 1); // L: handler func args | ||
656 | #endif // L: ERROR_FULL_STACK | ||
657 | |||
658 | _rc = lua_pcall(_L, nargs, LUA_MULTRET, ERROR_FULL_STACK); // L: retvals|err | ||
659 | |||
660 | #if ERROR_FULL_STACK | ||
661 | lua_remove(_L, 1); // L: retvals|error | ||
662 | #endif // ERROR_FULL_STACK | ||
663 | |||
664 | // in case of error and if it exists, fetch stack trace from registry and push it | ||
665 | push_stack_trace(_L, _rc, 1); // L: retvals|error [trace] | ||
666 | |||
667 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p body: %s (%s)\n" INDENT_END(U), _L, get_errcode_name(_rc), kCancelError.equals(_L, 1) ? "cancelled" : lua_typename(_L, lua_type(_L, 1)))); | ||
668 | // Call finalizers, if the script has set them up. | ||
669 | // | ||
670 | int _rc2{ run_finalizers(_L, _rc) }; | ||
671 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "Lane %p finalizer: %s\n" INDENT_END(U), _L, get_errcode_name(_rc2))); | ||
672 | if (_rc2 != LUA_OK) { // Error within a finalizer! | ||
673 | // the finalizer generated an error, and left its own error message [and stack trace] on the stack | ||
674 | _rc = _rc2; // we're overruling the earlier script error or normal return | ||
675 | } | ||
676 | lane_->waiting_on = nullptr; // just in case | ||
677 | if (selfdestruct_remove(lane_)) { // check and remove (under lock!) | ||
678 | // We're a free-running thread and no-one's there to clean us up. | ||
679 | lua_close(lane_->L); | ||
680 | lane_->L = nullptr; // just in case | ||
681 | lane_->U->selfdestructMutex.lock(); | ||
682 | // done with lua_close(), terminal shutdown sequence may proceed | ||
683 | lane_->U->selfdestructingCount.fetch_sub(1, std::memory_order_release); | ||
684 | lane_->U->selfdestructMutex.unlock(); | ||
685 | |||
686 | // we destroy our jthread member from inside the thread body, so we have to detach so that we don't try to join, as this doesn't seem a good idea | ||
687 | lane_->thread.detach(); | ||
688 | delete lane_; | ||
689 | lane_ = nullptr; | ||
690 | } | ||
691 | } | ||
692 | if (lane_) { | ||
693 | // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them | ||
694 | |||
695 | Lane::Status const _st = (_rc == LUA_OK) ? Lane::Done : kCancelError.equals(_L, 1) ? Lane::Cancelled : Lane::Error; | ||
696 | |||
697 | { | ||
698 | // 'doneMutex' protects the -> Done|Error|Cancelled state change | ||
699 | std::lock_guard lock{ lane_->doneMutex }; | ||
700 | lane_->status = _st; | ||
701 | lane_->doneCondVar.notify_one(); // wake up master (while 'lane_->doneMutex' is on) | ||
702 | } | ||
703 | } | ||
704 | } | ||
705 | |||
706 | // ################################################################################################# | ||
707 | |||
708 | // --- If a client wants to transfer stuff of a given module from the current state to another Lane, the module must be required | 163 | // --- If a client wants to transfer stuff of a given module from the current state to another Lane, the module must be required |
709 | // with lanes.require, that will call the regular 'require', then populate the lookup database in the source lane | 164 | // with lanes.require, that will call the regular 'require', then populate the lookup database in the source lane |
710 | // module = lanes.require( "modname") | 165 | // module = lanes.require( "modname") |
@@ -750,9 +205,6 @@ LUAG_FUNC(register) | |||
750 | 205 | ||
751 | // ################################################################################################# | 206 | // ################################################################################################# |
752 | 207 | ||
753 | // xxh64 of string "kLaneGC" generated at https://www.pelock.com/products/hash-calculator | ||
754 | static constexpr UniqueKey kLaneGC{ 0x5D6122141727F960ull }; | ||
755 | |||
756 | //--- | 208 | //--- |
757 | // lane_ud = lane_new( function | 209 | // lane_ud = lane_new( function |
758 | // , [libs_str] | 210 | // , [libs_str] |
@@ -1039,328 +491,6 @@ LUAG_FUNC(lane_new) | |||
1039 | return 1; | 491 | return 1; |
1040 | } | 492 | } |
1041 | 493 | ||
1042 | // ################################################################################################# | ||
1043 | |||
1044 | // = thread_gc( lane_ud ) | ||
1045 | // | ||
1046 | // Cleanup for a thread userdata. If the thread is still executing, leave it | ||
1047 | // alive as a free-running thread (will clean up itself). | ||
1048 | // | ||
1049 | // * Why NOT cancel/kill a loose thread: | ||
1050 | // | ||
1051 | // At least timer system uses a free-running thread, they should be handy | ||
1052 | // and the issue of canceling/killing threads at gc is not very nice, either | ||
1053 | // (would easily cause waits at gc cycle, which we don't want). | ||
1054 | // | ||
1055 | [[nodiscard]] static int lane_gc(lua_State* L_) | ||
1056 | { | ||
1057 | bool _have_gc_cb{ false }; | ||
1058 | Lane* const _lane{ ToLane(L_, 1) }; // L_: ud | ||
1059 | |||
1060 | // if there a gc callback? | ||
1061 | lua_getiuservalue(L_, 1, 1); // L_: ud uservalue | ||
1062 | kLaneGC.pushKey(L_); // L_: ud uservalue __gc | ||
1063 | lua_rawget(L_, -2); // L_: ud uservalue gc_cb|nil | ||
1064 | if (!lua_isnil(L_, -1)) { | ||
1065 | lua_remove(L_, -2); // L_: ud gc_cb|nil | ||
1066 | lua_pushstring(L_, _lane->debugName); // L_: ud gc_cb name | ||
1067 | _have_gc_cb = true; | ||
1068 | } else { | ||
1069 | lua_pop(L_, 2); // L_: ud | ||
1070 | } | ||
1071 | |||
1072 | // We can read 'lane->status' without locks, but not wait for it | ||
1073 | if (_lane->status < Lane::Done) { | ||
1074 | // still running: will have to be cleaned up later | ||
1075 | selfdestruct_add(_lane); | ||
1076 | assert(_lane->selfdestruct_next); | ||
1077 | if (_have_gc_cb) { | ||
1078 | lua_pushliteral(L_, "selfdestruct"); // L_: ud gc_cb name status | ||
1079 | lua_call(L_, 2, 0); // L_: ud | ||
1080 | } | ||
1081 | return 0; | ||
1082 | } else if (_lane->L) { | ||
1083 | // no longer accessing the Lua VM: we can close right now | ||
1084 | lua_close(_lane->L); | ||
1085 | _lane->L = nullptr; | ||
1086 | // just in case, but s will be freed soon so... | ||
1087 | _lane->debugName = "<gc>"; | ||
1088 | } | ||
1089 | |||
1090 | // Clean up after a (finished) thread | ||
1091 | delete _lane; | ||
1092 | |||
1093 | // do this after lane cleanup in case the callback triggers an error | ||
1094 | if (_have_gc_cb) { | ||
1095 | lua_pushliteral(L_, "closed"); // L_: ud gc_cb name status | ||
1096 | lua_call(L_, 2, 0); // L_: ud | ||
1097 | } | ||
1098 | return 0; | ||
1099 | } | ||
1100 | |||
1101 | // ################################################################################################# | ||
1102 | |||
1103 | //--- | ||
1104 | // str= thread_status( lane ) | ||
1105 | // | ||
1106 | // Returns: "pending" not started yet | ||
1107 | // -> "running" started, doing its work.. | ||
1108 | // <-> "waiting" blocked in a receive() | ||
1109 | // -> "done" finished, results are there | ||
1110 | // / "error" finished at an error, error value is there | ||
1111 | // / "cancelled" execution cancelled by M (state gone) | ||
1112 | // | ||
1113 | [[nodiscard]] static char const* thread_status_string(Lane::Status status_) | ||
1114 | { | ||
1115 | char const* const _str{ | ||
1116 | (status_ == Lane::Pending) ? "pending" : | ||
1117 | (status_ == Lane::Running) ? "running" : // like in 'co.status()' | ||
1118 | (status_ == Lane::Waiting) ? "waiting" : | ||
1119 | (status_ == Lane::Done) ? "done" : | ||
1120 | (status_ == Lane::Error) ? "error" : | ||
1121 | (status_ == Lane::Cancelled) ? "cancelled" : | ||
1122 | nullptr | ||
1123 | }; | ||
1124 | return _str; | ||
1125 | } | ||
1126 | |||
1127 | // ################################################################################################# | ||
1128 | |||
1129 | void Lane::pushThreadStatus(lua_State* L_) | ||
1130 | { | ||
1131 | char const* const _str{ thread_status_string(status) }; | ||
1132 | LUA_ASSERT(L_, _str); | ||
1133 | |||
1134 | lua_pushstring(L_, _str); | ||
1135 | } | ||
1136 | |||
1137 | // ################################################################################################# | ||
1138 | |||
1139 | //--- | ||
1140 | // [...] | [nil, err_any, stack_tbl]= thread_join( lane_ud [, wait_secs=-1] ) | ||
1141 | // | ||
1142 | // timeout: returns nil | ||
1143 | // done: returns return values (0..N) | ||
1144 | // error: returns nil + error value [+ stack table] | ||
1145 | // cancelled: returns nil | ||
1146 | // | ||
1147 | LUAG_FUNC(thread_join) | ||
1148 | { | ||
1149 | Lane* const _lane{ ToLane(L_, 1) }; | ||
1150 | lua_State* const _L2{ _lane->L }; | ||
1151 | |||
1152 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; | ||
1153 | if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion | ||
1154 | lua_Duration const duration{ lua_tonumber(L_, 2) }; | ||
1155 | if (duration.count() >= 0.0) { | ||
1156 | _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration); | ||
1157 | } else { | ||
1158 | raise_luaL_argerror(L_, 2, "duration cannot be < 0"); | ||
1159 | } | ||
1160 | |||
1161 | } else if (!lua_isnoneornil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key | ||
1162 | raise_luaL_argerror(L_, 2, "incorrect duration type"); | ||
1163 | } | ||
1164 | |||
1165 | bool const done{ !_lane->thread.joinable() || _lane->waitForCompletion(_until) }; | ||
1166 | lua_settop(L_, 1); // L_: lane | ||
1167 | if (!done || !_L2) { | ||
1168 | lua_pushnil(L_); // L_: lane nil | ||
1169 | lua_pushliteral(L_, "timeout"); // L_: lane nil "timeout" | ||
1170 | return 2; | ||
1171 | } | ||
1172 | |||
1173 | STACK_CHECK_START_REL(L_, 0); // L_: lane | ||
1174 | // Thread is Done/Error/Cancelled; all ours now | ||
1175 | |||
1176 | int _ret{ 0 }; | ||
1177 | // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed | ||
1178 | // so store it in the userdata uservalue at a key that can't possibly collide | ||
1179 | _lane->securizeDebugName(L_); | ||
1180 | switch (_lane->status) { | ||
1181 | case Lane::Done: | ||
1182 | { | ||
1183 | int const _n{ lua_gettop(_L2) }; // whole L2 stack | ||
1184 | if ( | ||
1185 | (_n > 0) && | ||
1186 | (InterCopyContext{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }.inter_move(_n) != InterCopyResult::Success) | ||
1187 | ) { // L_: lane results L2: | ||
1188 | raise_luaL_error(L_, "tried to copy unsupported types"); | ||
1189 | } | ||
1190 | _ret = _n; | ||
1191 | } | ||
1192 | break; | ||
1193 | |||
1194 | case Lane::Error: | ||
1195 | { | ||
1196 | int const _n{ lua_gettop(_L2) }; // L_: lane L2: "err" [trace] | ||
1197 | STACK_GROW(L_, 3); | ||
1198 | lua_pushnil(L_); // L_: lane nil | ||
1199 | // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ... | ||
1200 | InterCopyContext _c{ _lane->U, DestState{ L_ }, SourceState{ _L2 }, {}, {}, {}, {}, {} }; | ||
1201 | if (_c.inter_move(_n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2: | ||
1202 | raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -_n)); | ||
1203 | } | ||
1204 | _ret = 1 + _n; | ||
1205 | } | ||
1206 | break; | ||
1207 | |||
1208 | case Lane::Cancelled: | ||
1209 | _ret = 0; | ||
1210 | break; | ||
1211 | |||
1212 | default: | ||
1213 | DEBUGSPEW_CODE(fprintf(stderr, "Status: %d\n", _lane->status)); | ||
1214 | LUA_ASSERT(L_, false); | ||
1215 | _ret = 0; | ||
1216 | } | ||
1217 | lua_close(_L2); | ||
1218 | _lane->L = nullptr; | ||
1219 | STACK_CHECK(L_, _ret); | ||
1220 | return _ret; | ||
1221 | } | ||
1222 | |||
1223 | // ################################################################################################# | ||
1224 | |||
1225 | // lane:__index(key,usr) -> value | ||
1226 | // | ||
1227 | // If key is found in the environment, return it | ||
1228 | // If key is numeric, wait until the thread returns and populate the environment with the return values | ||
1229 | // If the return values signal an error, propagate it | ||
1230 | // If key is "status" return the thread status | ||
1231 | // Else raise an error | ||
1232 | LUAG_FUNC(thread_index) | ||
1233 | { | ||
1234 | static constexpr int kSelf{ 1 }; | ||
1235 | static constexpr int kKey{ 2 }; | ||
1236 | Lane* const _lane{ ToLane(L_, kSelf) }; | ||
1237 | LUA_ASSERT(L_, lua_gettop(L_) == 2); | ||
1238 | |||
1239 | STACK_GROW(L_, 8); // up to 8 positions are needed in case of error propagation | ||
1240 | |||
1241 | // If key is numeric, wait until the thread returns and populate the environment with the return values | ||
1242 | if (lua_type(L_, kKey) == LUA_TNUMBER) { | ||
1243 | static constexpr int kUsr{ 3 }; | ||
1244 | // first, check that we don't already have an environment that holds the requested value | ||
1245 | { | ||
1246 | // If key is found in the uservalue, return it | ||
1247 | lua_getiuservalue(L_, kSelf, 1); | ||
1248 | lua_pushvalue(L_, kKey); | ||
1249 | lua_rawget(L_, kUsr); | ||
1250 | if (!lua_isnil(L_, -1)) { | ||
1251 | return 1; | ||
1252 | } | ||
1253 | lua_pop(L_, 1); | ||
1254 | } | ||
1255 | { | ||
1256 | // check if we already fetched the values from the thread or not | ||
1257 | lua_pushinteger(L_, 0); | ||
1258 | lua_rawget(L_, kUsr); | ||
1259 | bool const _fetched{ !lua_isnil(L_, -1) }; | ||
1260 | lua_pop(L_, 1); // back to our 2 args + uservalue on the stack | ||
1261 | if (!_fetched) { | ||
1262 | lua_pushinteger(L_, 0); | ||
1263 | lua_pushboolean(L_, 1); | ||
1264 | lua_rawset(L_, kUsr); | ||
1265 | // wait until thread has completed | ||
1266 | lua_pushcfunction(L_, LG_thread_join); | ||
1267 | lua_pushvalue(L_, kSelf); | ||
1268 | lua_call(L_, 1, LUA_MULTRET); // all return values are on the stack, at slots 4+ | ||
1269 | switch (_lane->status) { | ||
1270 | default: | ||
1271 | // this is an internal error, we probably never get here | ||
1272 | lua_settop(L_, 0); | ||
1273 | lua_pushliteral(L_, "Unexpected status: "); | ||
1274 | lua_pushstring(L_, thread_status_string(_lane->status)); | ||
1275 | lua_concat(L_, 2); | ||
1276 | raise_lua_error(L_); | ||
1277 | [[fallthrough]]; // fall through if we are killed, as we got nil, "killed" on the stack | ||
1278 | |||
1279 | case Lane::Done: // got regular return values | ||
1280 | { | ||
1281 | int const _nvalues{ lua_gettop(L_) - 3 }; | ||
1282 | for (int _i = _nvalues; _i > 0; --_i) { | ||
1283 | // pop the last element of the stack, to store it in the uservalue at its proper index | ||
1284 | lua_rawseti(L_, kUsr, _i); | ||
1285 | } | ||
1286 | } | ||
1287 | break; | ||
1288 | |||
1289 | case Lane::Error: // got 3 values: nil, errstring, callstack table | ||
1290 | // me[-2] could carry the stack table, but even | ||
1291 | // me[-1] is rather unnecessary (and undocumented); | ||
1292 | // use ':join()' instead. --AKa 22-Jan-2009 | ||
1293 | LUA_ASSERT(L_, lua_isnil(L_, 4) && !lua_isnil(L_, 5) && lua_istable(L_, 6)); | ||
1294 | // store errstring at key -1 | ||
1295 | lua_pushnumber(L_, -1); | ||
1296 | lua_pushvalue(L_, 5); | ||
1297 | lua_rawset(L_, kUsr); | ||
1298 | break; | ||
1299 | |||
1300 | case Lane::Cancelled: | ||
1301 | // do nothing | ||
1302 | break; | ||
1303 | } | ||
1304 | } | ||
1305 | lua_settop(L_, 3); // L_: self KEY ENV | ||
1306 | int const _key{ static_cast<int>(lua_tointeger(L_, kKey)) }; | ||
1307 | if (_key != -1) { | ||
1308 | lua_pushnumber(L_, -1); // L_: self KEY ENV -1 | ||
1309 | lua_rawget(L_, kUsr); // L_: self KEY ENV "error"|nil | ||
1310 | if (!lua_isnil(L_, -1)) { // L_: an error was stored | ||
1311 | // Note: Lua 5.1 interpreter is not prepared to show | ||
1312 | // non-string errors, so we use 'tostring()' here | ||
1313 | // to get meaningful output. --AKa 22-Jan-2009 | ||
1314 | // | ||
1315 | // Also, the stack dump we get is no good; it only | ||
1316 | // lists our internal Lanes functions. There seems | ||
1317 | // to be no way to switch it off, though. | ||
1318 | // | ||
1319 | // Level 3 should show the line where 'h[x]' was read | ||
1320 | // but this only seems to work for string messages | ||
1321 | // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 | ||
1322 | lua_getmetatable(L_, kSelf); // L_: self KEY ENV "error" mt | ||
1323 | lua_getfield(L_, -1, "cached_error"); // L_: self KEY ENV "error" mt error() | ||
1324 | lua_getfield(L_, -2, "cached_tostring"); // L_: self KEY ENV "error" mt error() tostring() | ||
1325 | lua_pushvalue(L_, 4); // L_: self KEY ENV "error" mt error() tostring() "error" | ||
1326 | lua_call(L_, 1, 1); // tostring(errstring) -- just in case // L_: self KEY ENV "error" mt error() "error" | ||
1327 | lua_pushinteger(L_, 3); // L_: self KEY ENV "error" mt error() "error" 3 | ||
1328 | lua_call(L_, 2, 0); // error(tostring(errstring), 3) -> doesn't return // L_: self KEY ENV "error" mt | ||
1329 | } else { | ||
1330 | lua_pop(L_, 1); // L_: self KEY ENV | ||
1331 | } | ||
1332 | } | ||
1333 | lua_rawgeti(L_, kUsr, _key); | ||
1334 | } | ||
1335 | return 1; | ||
1336 | } | ||
1337 | if (lua_type(L_, kKey) == LUA_TSTRING) { | ||
1338 | char const* const _keystr{ lua_tostring(L_, kKey) }; | ||
1339 | lua_settop(L_, 2); // keep only our original arguments on the stack | ||
1340 | if (strcmp(_keystr, "status") == 0) { | ||
1341 | _lane->pushThreadStatus(L_); // push the string representing the status | ||
1342 | return 1; | ||
1343 | } | ||
1344 | // return self.metatable[key] | ||
1345 | lua_getmetatable(L_, kSelf); // L_: self KEY mt | ||
1346 | lua_replace(L_, -3); // L_: mt KEY | ||
1347 | lua_rawget(L_, -2); // L_: mt value | ||
1348 | // only "cancel" and "join" are registered as functions, any other string will raise an error | ||
1349 | if (!lua_iscfunction(L_, -1)) { | ||
1350 | raise_luaL_error(L_, "can't index a lane with '%s'", _keystr); | ||
1351 | } | ||
1352 | return 1; | ||
1353 | } | ||
1354 | // unknown key | ||
1355 | lua_getmetatable(L_, kSelf); // L_: mt | ||
1356 | lua_getfield(L_, -1, "cached_error"); // L_: mt error | ||
1357 | lua_pushliteral(L_, "Unknown key: "); // L_: mt error "Unknown key: " | ||
1358 | lua_pushvalue(L_, kKey); // L_: mt error "Unknown key: " k | ||
1359 | lua_concat(L_, 2); // L_: mt error "Unknown key: <k>" | ||
1360 | lua_call(L_, 1, 0); // error( "Unknown key: " .. key) -> doesn't return // L_: mt | ||
1361 | return 0; | ||
1362 | } | ||
1363 | |||
1364 | // ################################################################################################ | 494 | // ################################################################################################ |
1365 | 495 | ||
1366 | #if HAVE_LANE_TRACKING() | 496 | #if HAVE_LANE_TRACKING() |
@@ -1649,27 +779,7 @@ LUAG_FUNC(configure) | |||
1649 | 779 | ||
1650 | // prepare the metatable for threads | 780 | // prepare the metatable for threads |
1651 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } | 781 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } |
1652 | // | 782 | Lane::PushMetatable(L_); |
1653 | if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: settings M mt | ||
1654 | lua_pushcfunction(L_, lane_gc); // L_: settings M mt lane_gc | ||
1655 | lua_setfield(L_, -2, "__gc"); // L_: settings M mt | ||
1656 | lua_pushcfunction(L_, LG_thread_index); // L_: settings M mt LG_thread_index | ||
1657 | lua_setfield(L_, -2, "__index"); // L_: settings M mt | ||
1658 | lua_getglobal(L_, "error"); // L_: settings M mt error | ||
1659 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
1660 | lua_setfield(L_, -2, "cached_error"); // L_: settings M mt | ||
1661 | lua_getglobal(L_, "tostring"); // L_: settings M mt tostring | ||
1662 | LUA_ASSERT(L_, lua_isfunction(L_, -1)); | ||
1663 | lua_setfield(L_, -2, "cached_tostring"); // L_: settings M mt | ||
1664 | lua_pushcfunction(L_, LG_thread_join); // L_: settings M mt LG_thread_join | ||
1665 | lua_setfield(L_, -2, "join"); // L_: settings M mt | ||
1666 | lua_pushcfunction(L_, LG_get_debug_threadname); // L_: settings M mt LG_get_debug_threadname | ||
1667 | lua_setfield(L_, -2, "get_debug_threadname"); // L_: settings M mt | ||
1668 | lua_pushcfunction(L_, LG_thread_cancel); // L_: settings M mt LG_thread_cancel | ||
1669 | lua_setfield(L_, -2, "cancel"); // L_: settings M mt | ||
1670 | lua_pushliteral(L_, kLaneMetatableName); // L_: settings M mt "Lane" | ||
1671 | lua_setfield(L_, -2, "__metatable"); // L_: settings M mt | ||
1672 | } | ||
1673 | 783 | ||
1674 | lua_pushcclosure(L_, LG_lane_new, 1); // L_: settings M lane_new | 784 | lua_pushcclosure(L_, LG_lane_new, 1); // L_: settings M lane_new |
1675 | lua_setfield(L_, -2, "lane_new"); // L_: settings M | 785 | lua_setfield(L_, -2, "lane_new"); // L_: settings M |
diff --git a/src/linda.cpp b/src/linda.cpp index fedcd01..ad40eef 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
@@ -32,7 +32,7 @@ THE SOFTWARE. | |||
32 | 32 | ||
33 | #include "linda.h" | 33 | #include "linda.h" |
34 | 34 | ||
35 | #include "lanes_private.h" | 35 | #include "lane.h" |
36 | #include "lindafactory.h" | 36 | #include "lindafactory.h" |
37 | #include "tools.h" | 37 | #include "tools.h" |
38 | 38 | ||
diff --git a/src/state.cpp b/src/state.cpp index 563fbc2..7309e50 100644 --- a/src/state.cpp +++ b/src/state.cpp | |||
@@ -34,8 +34,8 @@ THE SOFTWARE. | |||
34 | #include "state.h" | 34 | #include "state.h" |
35 | 35 | ||
36 | #include "intercopycontext.h" | 36 | #include "intercopycontext.h" |
37 | #include "lane.h" | ||
37 | #include "lanes.h" | 38 | #include "lanes.h" |
38 | #include "lanes_private.h" | ||
39 | #include "tools.h" | 39 | #include "tools.h" |
40 | #include "universe.h" | 40 | #include "universe.h" |
41 | 41 | ||
diff --git a/src/tracker.cpp b/src/tracker.cpp index e6affb2..76b814d 100644 --- a/src/tracker.cpp +++ b/src/tracker.cpp | |||
@@ -24,7 +24,7 @@ THE SOFTWARE. | |||
24 | =============================================================================== | 24 | =============================================================================== |
25 | */ | 25 | */ |
26 | #include "tracker.h" | 26 | #include "tracker.h" |
27 | #include "lanes_private.h" | 27 | #include "lane.h" |
28 | 28 | ||
29 | // ################################################################################################# | 29 | // ################################################################################################# |
30 | 30 | ||
diff --git a/src/universe.cpp b/src/universe.cpp index f05cc6f..dd293f2 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
@@ -32,7 +32,7 @@ THE SOFTWARE. | |||
32 | 32 | ||
33 | #include "deep.h" | 33 | #include "deep.h" |
34 | #include "keeper.h" | 34 | #include "keeper.h" |
35 | #include "lanes_private.h" | 35 | #include "lane.h" |
36 | 36 | ||
37 | // ################################################################################################# | 37 | // ################################################################################################# |
38 | 38 | ||