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