aboutsummaryrefslogtreecommitdiff
path: root/src/lane.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lane.cpp')
-rw-r--r--src/lane.cpp916
1 files changed, 916 insertions, 0 deletions
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}