aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-14 14:15:01 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-14 14:15:01 +0200
commit9589d1941671818f78d9894cfc9485054d62d122 (patch)
treec804998ba5cf89b75cb3d27052ee469fd4986595 /src
parent1013970853e6acfd60591a89ae08cc40c64bee06 (diff)
downloadlanes-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/Makefile2
-rw-r--r--src/cancel.cpp2
-rw-r--r--src/lane.cpp916
-rw-r--r--src/lane.h (renamed from src/lanes_private.h)36
-rw-r--r--src/lanes.cpp894
-rw-r--r--src/linda.cpp2
-rw-r--r--src/state.cpp2
-rw-r--r--src/tracker.cpp2
-rw-r--r--src/universe.cpp2
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
8MODULE=lanes 8MODULE=lanes
9 9
10SRC=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 10SRC=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
12OBJ=$(SRC:.cpp=.o) 12OBJ=$(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
4Copyright (C) 2024 Benoit Germain <bnt.germain@gmail.com>
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE 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
45LUAG_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//
62LUAG_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
80LUAG_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
100LUAG_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//
122LUAG_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
207LUAG_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
346struct errcode_name
347{
348 int code;
349 char const* name;
350};
351
352static 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};
361static 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
393static 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
466static 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 */
581static 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
624static 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
775Lane::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
786Lane::~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
797void 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 }
819void 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
844void 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
855void 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
874void 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
910bool 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
24static constexpr RegistryUniqueKey kFinalizerRegKey{ 0xFE936BFAA718FEEAull };
25
26// xxh64 of string "kExtendedStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator
27static constexpr RegistryUniqueKey kExtendedStackTraceRegKey{ 0x38147AD48FB426E2ull }; // used as registry key
28
29// xxh64 of string "kLaneGC" generated at https://www.pelock.com/products/hash-calculator
30static constexpr UniqueKey kLaneGC{ 0x5D6122141727F960ull };
31
32// xxh64 of string "kLanePointerRegKey" generated at https://www.pelock.com/products/hash-calculator
33static 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// #################################################################################################
105static 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
107Lane::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
118bool 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
128static void lane_main(Lane* lane_);
129void 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
146void 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
168static 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
180static constexpr RegistryUniqueKey kFinalizerRegKey{ 0xFE936BFAA718FEEAull };
181
182// #################################################################################################
183
184Lane::~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//
204LUAG_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
221static 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 */
338static 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
426static constexpr RegistryUniqueKey kExtendedStackTraceRegKey{ 0x38147AD48FB426E2ull }; // used as registry key
427
428LUAG_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
514void 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
536LUAG_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
550LUAG_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
560LUAG_FUNC(set_thread_priority) 136LUAG_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
590struct errcode_name
591{
592 int code;
593 char const* name;
594};
595
596static 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};
605static 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
618static 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
754static 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
1129void 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//
1147LUAG_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
1232LUAG_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