diff options
| author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-07 09:42:09 +0200 |
|---|---|---|
| committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-13 18:15:46 +0200 |
| commit | d093c5555ec439affcfbecdceabfb122aa8c2f73 (patch) | |
| tree | de148388b0382d87bcc2caed72625e3dec829bda /src | |
| parent | ebb0837588336e32fc1258a3838673afdd31ee71 (diff) | |
| download | lanes-d093c5555ec439affcfbecdceabfb122aa8c2f73.tar.gz lanes-d093c5555ec439affcfbecdceabfb122aa8c2f73.tar.bz2 lanes-d093c5555ec439affcfbecdceabfb122aa8c2f73.zip | |
Some more code refactorization
Diffstat (limited to 'src')
| -rw-r--r-- | src/compat.cpp | 2 | ||||
| -rw-r--r-- | src/compat.h | 2 | ||||
| -rw-r--r-- | src/lanes.cpp | 135 | ||||
| -rw-r--r-- | src/lanes_private.h | 10 | ||||
| -rw-r--r-- | src/lindafactory.cpp | 5 | ||||
| -rw-r--r-- | src/state.cpp | 107 | ||||
| -rw-r--r-- | src/tools.cpp | 2 | ||||
| -rw-r--r-- | src/universe.cpp | 116 | ||||
| -rw-r--r-- | src/universe.h | 36 |
9 files changed, 223 insertions, 192 deletions
diff --git a/src/compat.cpp b/src/compat.cpp index 1d38917..336f716 100644 --- a/src/compat.cpp +++ b/src/compat.cpp | |||
| @@ -29,7 +29,7 @@ LuaType luaG_getmodule(lua_State* L_, char const* name_) | |||
| 29 | // ################################################################################################# | 29 | // ################################################################################################# |
| 30 | 30 | ||
| 31 | // Copied from Lua 5.2 loadlib.c | 31 | // Copied from Lua 5.2 loadlib.c |
| 32 | static int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_) | 32 | int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_) |
| 33 | { | 33 | { |
| 34 | lua_getfield(L_, idx_, fname_); | 34 | lua_getfield(L_, idx_, fname_); |
| 35 | if (lua_istable(L_, -1)) | 35 | if (lua_istable(L_, -1)) |
diff --git a/src/compat.h b/src/compat.h index 3a61268..bf22f10 100644 --- a/src/compat.h +++ b/src/compat.h | |||
| @@ -68,6 +68,8 @@ inline int lua504_dump(lua_State* L_, lua_Writer writer_, void* data_, [[maybe_u | |||
| 68 | } | 68 | } |
| 69 | #define LUA_LOADED_TABLE "_LOADED" // // doesn't exist in Lua 5.1 | 69 | #define LUA_LOADED_TABLE "_LOADED" // // doesn't exist in Lua 5.1 |
| 70 | 70 | ||
| 71 | int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_); | ||
| 72 | |||
| 71 | #endif // LUA_VERSION_NUM == 501 | 73 | #endif // LUA_VERSION_NUM == 501 |
| 72 | 74 | ||
| 73 | // ################################################################################################# | 75 | // ################################################################################################# |
diff --git a/src/lanes.cpp b/src/lanes.cpp index bce75a6..90f0f9f 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
| @@ -393,14 +393,6 @@ static void push_stack_trace(lua_State* L_, int rc_, int stk_base_) | |||
| 393 | // ########################################### Threads ############################################# | 393 | // ########################################### Threads ############################################# |
| 394 | // ################################################################################################# | 394 | // ################################################################################################# |
| 395 | 395 | ||
| 396 | // | ||
| 397 | // Protects modifying the selfdestruct chain | ||
| 398 | |||
| 399 | #define SELFDESTRUCT_END ((Lane*) (-1)) | ||
| 400 | // | ||
| 401 | // The chain is ended by '(Lane*)(-1)', not nullptr: | ||
| 402 | // 'selfdestructFirst -> ... -> ... -> (-1)' | ||
| 403 | |||
| 404 | /* | 396 | /* |
| 405 | * Add the lane to selfdestruct chain; the ones still running at the end of the | 397 | * Add the lane to selfdestruct chain; the ones still running at the end of the |
| 406 | * whole process will be cancelled. | 398 | * whole process will be cancelled. |
| @@ -446,99 +438,6 @@ static void selfdestruct_add(Lane* lane_) | |||
| 446 | 438 | ||
| 447 | // ################################################################################################# | 439 | // ################################################################################################# |
| 448 | 440 | ||
| 449 | // process end: cancel any still free-running threads | ||
| 450 | [[nodiscard]] static int universe_gc(lua_State* L_) | ||
| 451 | { | ||
| 452 | Universe* const U{ lua_tofulluserdata<Universe>(L_, 1) }; | ||
| 453 | lua_Duration const shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; | ||
| 454 | [[maybe_unused]] char const* const op_string{ lua_tostring(L_, lua_upvalueindex(2)) }; | ||
| 455 | CancelOp const op{ which_cancel_op(op_string) }; | ||
| 456 | |||
| 457 | if (U->selfdestructFirst != SELFDESTRUCT_END) { | ||
| 458 | // Signal _all_ still running threads to exit (including the timer thread) | ||
| 459 | { | ||
| 460 | std::lock_guard<std::mutex> guard{ U->selfdestructMutex }; | ||
| 461 | Lane* lane{ U->selfdestructFirst }; | ||
| 462 | lua_Duration timeout{ 1us }; | ||
| 463 | while (lane != SELFDESTRUCT_END) { | ||
| 464 | // attempt the requested cancel with a small timeout. | ||
| 465 | // if waiting on a linda, they will raise a cancel_error. | ||
| 466 | // if a cancellation hook is desired, it will be installed to try to raise an error | ||
| 467 | if (lane->thread.joinable()) { | ||
| 468 | std::ignore = thread_cancel(lane, op, 1, timeout, true); | ||
| 469 | } | ||
| 470 | lane = lane->selfdestruct_next; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | // When noticing their cancel, the lanes will remove themselves from the selfdestruct chain. | ||
| 475 | { | ||
| 476 | std::chrono::time_point<std::chrono::steady_clock> t_until{ std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(shutdown_timeout) }; | ||
| 477 | |||
| 478 | while (U->selfdestructFirst != SELFDESTRUCT_END) { | ||
| 479 | // give threads time to act on their cancel | ||
| 480 | std::this_thread::yield(); | ||
| 481 | // count the number of cancelled thread that didn't have the time to act yet | ||
| 482 | int n{ 0 }; | ||
| 483 | { | ||
| 484 | std::lock_guard<std::mutex> guard{ U->selfdestructMutex }; | ||
| 485 | Lane* lane{ U->selfdestructFirst }; | ||
| 486 | while (lane != SELFDESTRUCT_END) { | ||
| 487 | if (lane->cancelRequest != CancelRequest::None) | ||
| 488 | ++n; | ||
| 489 | lane = lane->selfdestruct_next; | ||
| 490 | } | ||
| 491 | } | ||
| 492 | // if timeout elapsed, or we know all threads have acted, stop waiting | ||
| 493 | std::chrono::time_point<std::chrono::steady_clock> t_now = std::chrono::steady_clock::now(); | ||
| 494 | if (n == 0 || (t_now >= t_until)) { | ||
| 495 | DEBUGSPEW_CODE(fprintf(stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, shutdown_timeout.count())); | ||
| 496 | break; | ||
| 497 | } | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | // If some lanes are currently cleaning after themselves, wait until they are done. | ||
| 502 | // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). | ||
| 503 | while (U->selfdestructingCount.load(std::memory_order_acquire) > 0) { | ||
| 504 | std::this_thread::yield(); | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | // If after all this, we still have some free-running lanes, it's an external user error, they should have stopped appropriately | ||
| 509 | { | ||
| 510 | std::lock_guard<std::mutex> guard{ U->selfdestructMutex }; | ||
| 511 | Lane* lane{ U->selfdestructFirst }; | ||
| 512 | if (lane != SELFDESTRUCT_END) { | ||
| 513 | // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) | ||
| 514 | raise_luaL_error(L_, "Zombie thread %s refuses to die!", lane->debugName); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | // no need to mutex-protect this as all threads in the universe are gone at that point | ||
| 519 | if (U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer | ||
| 520 | [[maybe_unused]] int const prev_ref_count{ U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) }; | ||
| 521 | LUA_ASSERT(L_, prev_ref_count == 1); // this should be the last reference | ||
| 522 | DeepFactory::DeleteDeepObject(L_, U->timerLinda); | ||
| 523 | U->timerLinda = nullptr; | ||
| 524 | } | ||
| 525 | |||
| 526 | close_keepers(U); | ||
| 527 | |||
| 528 | // remove the protected allocator, if any | ||
| 529 | U->protectedAllocator.removeFrom(L_); | ||
| 530 | |||
| 531 | U->Universe::~Universe(); | ||
| 532 | |||
| 533 | // universe is no longer available (nor necessary) | ||
| 534 | // we need to do this in case some deep userdata objects were created before Lanes was initialized, | ||
| 535 | // as potentially they will be garbage collected after Lanes at application shutdown | ||
| 536 | universe_store(L_, nullptr); | ||
| 537 | return 0; | ||
| 538 | } | ||
| 539 | |||
| 540 | // ################################################################################################# | ||
| 541 | |||
| 542 | //--- | 441 | //--- |
| 543 | // = _single( [cores_uint=1] ) | 442 | // = _single( [cores_uint=1] ) |
| 544 | // | 443 | // |
| @@ -1526,14 +1425,13 @@ LUAG_FUNC(threads) | |||
| 1526 | Universe* const U{ universe_get(L_) }; | 1425 | Universe* const U{ universe_get(L_) }; |
| 1527 | 1426 | ||
| 1528 | // List _all_ still running threads | 1427 | // List _all_ still running threads |
| 1529 | // | ||
| 1530 | std::lock_guard<std::mutex> guard{ U->trackingMutex }; | 1428 | std::lock_guard<std::mutex> guard{ U->trackingMutex }; |
| 1531 | if (U->trackingFirst && U->trackingFirst != TRACKING_END) { | 1429 | if (U->trackingFirst && U->trackingFirst != TRACKING_END) { |
| 1532 | Lane* lane{ U->trackingFirst }; | 1430 | Lane* lane{ U->trackingFirst }; |
| 1533 | int index{ 0 }; | 1431 | int index{ 0 }; |
| 1534 | lua_newtable(L_); // L_: {} | 1432 | lua_newtable(L_); // L_: {} |
| 1535 | while (lane != TRACKING_END) { | 1433 | while (lane != TRACKING_END) { |
| 1536 | // insert a { name, status } tuple, so that several lanes with the same name can't clobber each other | 1434 | // insert a { name='<name>', status='<status>' } tuple, so that several lanes with the same name can't clobber each other |
| 1537 | lua_createtable(L_, 0, 2); // L_: {} {} | 1435 | lua_createtable(L_, 0, 2); // L_: {} {} |
| 1538 | lua_pushstring(L_, lane->debugName); // L_: {} {} "name" | 1436 | lua_pushstring(L_, lane->debugName); // L_: {} {} "name" |
| 1539 | lua_setfield(L_, -2, "name"); // L_: {} {} | 1437 | lua_setfield(L_, -2, "name"); // L_: {} {} |
| @@ -1622,17 +1520,20 @@ LUAG_FUNC(wakeup_conv) | |||
| 1622 | // ################################################################################################# | 1520 | // ################################################################################################# |
| 1623 | 1521 | ||
| 1624 | extern int LG_linda(lua_State* L_); | 1522 | extern int LG_linda(lua_State* L_); |
| 1625 | static struct luaL_Reg const lanes_functions[] = { | 1523 | |
| 1626 | { "linda", LG_linda }, | 1524 | namespace global { |
| 1627 | { "now_secs", LG_now_secs }, | 1525 | static struct luaL_Reg const sLanesFunctions[] = { |
| 1628 | { "wakeup_conv", LG_wakeup_conv }, | 1526 | { "linda", LG_linda }, |
| 1629 | { "set_thread_priority", LG_set_thread_priority }, | 1527 | { "now_secs", LG_now_secs }, |
| 1630 | { "set_thread_affinity", LG_set_thread_affinity }, | 1528 | { "wakeup_conv", LG_wakeup_conv }, |
| 1631 | { "nameof", luaG_nameof }, | 1529 | { "set_thread_priority", LG_set_thread_priority }, |
| 1632 | { "register", LG_register }, | 1530 | { "set_thread_affinity", LG_set_thread_affinity }, |
| 1633 | { "set_singlethreaded", LG_set_singlethreaded }, | 1531 | { "nameof", luaG_nameof }, |
| 1634 | { nullptr, nullptr } | 1532 | { "register", LG_register }, |
| 1635 | }; | 1533 | { "set_singlethreaded", LG_set_singlethreaded }, |
| 1534 | { nullptr, nullptr } | ||
| 1535 | }; | ||
| 1536 | } // namespace global | ||
| 1636 | 1537 | ||
| 1637 | // ################################################################################################# | 1538 | // ################################################################################################# |
| 1638 | 1539 | ||
| @@ -1715,7 +1616,7 @@ LUAG_FUNC(configure) | |||
| 1715 | lua_pushnil(L_); // L_: settings M nil | 1616 | lua_pushnil(L_); // L_: settings M nil |
| 1716 | lua_setfield(L_, -2, "configure"); // L_: settings M | 1617 | lua_setfield(L_, -2, "configure"); // L_: settings M |
| 1717 | // add functions to the module's table | 1618 | // add functions to the module's table |
| 1718 | luaG_registerlibfuncs(L_, lanes_functions); | 1619 | luaG_registerlibfuncs(L_, global::sLanesFunctions); |
| 1719 | #if HAVE_LANE_TRACKING() | 1620 | #if HAVE_LANE_TRACKING() |
| 1720 | // register core.threads() only if settings say it should be available | 1621 | // register core.threads() only if settings say it should be available |
| 1721 | if (U->trackingFirst != nullptr) { | 1622 | if (U->trackingFirst != nullptr) { |
| @@ -1739,7 +1640,7 @@ LUAG_FUNC(configure) | |||
| 1739 | // prepare the metatable for threads | 1640 | // prepare the metatable for threads |
| 1740 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } | 1641 | // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } |
| 1741 | // | 1642 | // |
| 1742 | if (luaL_newmetatable(L_, "Lane")) { // L_: settings M mt | 1643 | if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: settings M mt |
| 1743 | lua_pushcfunction(L_, lane_gc); // L_: settings M mt lane_gc | 1644 | lua_pushcfunction(L_, lane_gc); // L_: settings M mt lane_gc |
| 1744 | lua_setfield(L_, -2, "__gc"); // L_: settings M mt | 1645 | lua_setfield(L_, -2, "__gc"); // L_: settings M mt |
| 1745 | lua_pushcfunction(L_, LG_thread_index); // L_: settings M mt LG_thread_index | 1646 | lua_pushcfunction(L_, LG_thread_index); // L_: settings M mt LG_thread_index |
| @@ -1756,7 +1657,7 @@ LUAG_FUNC(configure) | |||
| 1756 | lua_setfield(L_, -2, "get_debug_threadname"); // L_: settings M mt | 1657 | lua_setfield(L_, -2, "get_debug_threadname"); // L_: settings M mt |
| 1757 | lua_pushcfunction(L_, LG_thread_cancel); // L_: settings M mt LG_thread_cancel | 1658 | lua_pushcfunction(L_, LG_thread_cancel); // L_: settings M mt LG_thread_cancel |
| 1758 | lua_setfield(L_, -2, "cancel"); // L_: settings M mt | 1659 | lua_setfield(L_, -2, "cancel"); // L_: settings M mt |
| 1759 | lua_pushliteral(L_, "Lane"); // L_: settings M mt "Lane" | 1660 | lua_pushliteral(L_, kLaneMetatableName); // L_: settings M mt "Lane" |
| 1760 | lua_setfield(L_, -2, "__metatable"); // L_: settings M mt | 1661 | lua_setfield(L_, -2, "__metatable"); // L_: settings M mt |
| 1761 | } | 1662 | } |
| 1762 | 1663 | ||
diff --git a/src/lanes_private.h b/src/lanes_private.h index 309b632..196a346 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h | |||
| @@ -10,6 +10,14 @@ | |||
| 10 | #include <stop_token> | 10 | #include <stop_token> |
| 11 | #include <thread> | 11 | #include <thread> |
| 12 | 12 | ||
| 13 | // The chain is ended by '(Lane*)(-1)', not nullptr: 'selfdestructFirst -> ... -> ... -> (-1)' | ||
| 14 | #define SELFDESTRUCT_END ((Lane*) (-1)) | ||
| 15 | |||
| 16 | // must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) | ||
| 17 | #define kLaneMetatableName "Lane" | ||
| 18 | #define kLanesLibName "lanes" | ||
| 19 | #define kLanesCoreLibName kLanesLibName ".core" | ||
| 20 | |||
| 13 | // NOTE: values to be changed by either thread, during execution, without | 21 | // NOTE: values to be changed by either thread, during execution, without |
| 14 | // locking, are marked "volatile" | 22 | // locking, are marked "volatile" |
| 15 | // | 23 | // |
| @@ -102,5 +110,5 @@ static constexpr RegistryUniqueKey kLanePointerRegKey{ 0x2D8CF03FE9F0A51Aull }; | |||
| 102 | // | 110 | // |
| 103 | [[nodiscard]] inline Lane* ToLane(lua_State* L_, int i_) | 111 | [[nodiscard]] inline Lane* ToLane(lua_State* L_, int i_) |
| 104 | { | 112 | { |
| 105 | return *(static_cast<Lane**>(luaL_checkudata(L_, i_, "Lane"))); | 113 | return *(static_cast<Lane**>(luaL_checkudata(L_, i_, kLaneMetatableName))); |
| 106 | } | 114 | } |
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp index 1a8782e..0ec5a0a 100644 --- a/src/lindafactory.cpp +++ b/src/lindafactory.cpp | |||
| @@ -34,6 +34,9 @@ THE SOFTWARE. | |||
| 34 | 34 | ||
| 35 | #include "linda.h" | 35 | #include "linda.h" |
| 36 | 36 | ||
| 37 | // must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) | ||
| 38 | #define kLindaMetatableName "Linda" | ||
| 39 | |||
| 37 | // ################################################################################################# | 40 | // ################################################################################################# |
| 38 | 41 | ||
| 39 | void LindaFactory::createMetatable(lua_State* L_) const | 42 | void LindaFactory::createMetatable(lua_State* L_) const |
| @@ -45,7 +48,7 @@ void LindaFactory::createMetatable(lua_State* L_) const | |||
| 45 | lua_setfield(L_, -2, "__index"); | 48 | lua_setfield(L_, -2, "__index"); |
| 46 | 49 | ||
| 47 | // protect metatable from external access | 50 | // protect metatable from external access |
| 48 | lua_pushliteral(L_, "Linda"); | 51 | lua_pushliteral(L_, kLindaMetatableName); |
| 49 | lua_setfield(L_, -2, "__metatable"); | 52 | lua_setfield(L_, -2, "__metatable"); |
| 50 | 53 | ||
| 51 | // the linda functions | 54 | // the linda functions |
diff --git a/src/state.cpp b/src/state.cpp index a3dfbcd..f894978 100644 --- a/src/state.cpp +++ b/src/state.cpp | |||
| @@ -34,6 +34,7 @@ THE SOFTWARE. | |||
| 34 | #include "state.h" | 34 | #include "state.h" |
| 35 | 35 | ||
| 36 | #include "lanes.h" | 36 | #include "lanes.h" |
| 37 | #include "lanes_private.h" | ||
| 37 | #include "tools.h" | 38 | #include "tools.h" |
| 38 | #include "universe.h" | 39 | #include "universe.h" |
| 39 | 40 | ||
| @@ -111,68 +112,71 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_) | |||
| 111 | [[nodiscard]] static int require_lanes_core(lua_State* L_) | 112 | [[nodiscard]] static int require_lanes_core(lua_State* L_) |
| 112 | { | 113 | { |
| 113 | // leaves a copy of 'lanes.core' module table on the stack | 114 | // leaves a copy of 'lanes.core' module table on the stack |
| 114 | luaL_requiref(L_, "lanes.core", luaopen_lanes_core, 0); | 115 | luaL_requiref(L_, kLanesCoreLibName, luaopen_lanes_core, 0); |
| 115 | return 1; | 116 | return 1; |
| 116 | } | 117 | } |
| 117 | 118 | ||
| 118 | // ################################################################################################# | 119 | // ################################################################################################# |
| 119 | 120 | ||
| 120 | static luaL_Reg const libs[] = { | 121 | namespace global |
| 121 | { LUA_LOADLIBNAME, luaopen_package }, | 122 | { |
| 122 | { LUA_TABLIBNAME, luaopen_table }, | 123 | static luaL_Reg const sLibs[] = { |
| 123 | { LUA_STRLIBNAME, luaopen_string }, | 124 | { "base", nullptr }, // ignore "base" (already acquired it) |
| 124 | { LUA_MATHLIBNAME, luaopen_math }, | 125 | #if LUA_VERSION_NUM >= 502 |
| 126 | #ifdef luaopen_bit32 | ||
| 127 | { LUA_BITLIBNAME, luaopen_bit32 }, | ||
| 128 | #endif | ||
| 129 | { LUA_COLIBNAME, luaopen_coroutine }, // Lua 5.2: coroutine is no longer a part of base! | ||
| 130 | #else // LUA_VERSION_NUM | ||
| 131 | { LUA_COLIBNAME, nullptr }, // Lua 5.1: part of base package | ||
| 132 | #endif // LUA_VERSION_NUM | ||
| 133 | { LUA_DBLIBNAME, luaopen_debug }, | ||
| 125 | #ifndef PLATFORM_XBOX // no os/io libs on xbox | 134 | #ifndef PLATFORM_XBOX // no os/io libs on xbox |
| 126 | { LUA_OSLIBNAME, luaopen_os }, | 135 | { LUA_IOLIBNAME, luaopen_io }, |
| 127 | { LUA_IOLIBNAME, luaopen_io }, | 136 | { LUA_OSLIBNAME, luaopen_os }, |
| 128 | #endif // PLATFORM_XBOX | 137 | #endif // PLATFORM_XBOX |
| 138 | { LUA_LOADLIBNAME, luaopen_package }, | ||
| 139 | { LUA_MATHLIBNAME, luaopen_math }, | ||
| 140 | { LUA_STRLIBNAME, luaopen_string }, | ||
| 141 | { LUA_TABLIBNAME, luaopen_table }, | ||
| 129 | #if LUA_VERSION_NUM >= 503 | 142 | #if LUA_VERSION_NUM >= 503 |
| 130 | { LUA_UTF8LIBNAME, luaopen_utf8 }, | 143 | { LUA_UTF8LIBNAME, luaopen_utf8 }, |
| 131 | #endif | 144 | #endif |
| 132 | #if LUA_VERSION_NUM >= 502 | ||
| 133 | #ifdef luaopen_bit32 | ||
| 134 | { LUA_BITLIBNAME, luaopen_bit32 }, | ||
| 135 | #endif | ||
| 136 | { LUA_COLIBNAME, luaopen_coroutine }, // Lua 5.2: coroutine is no longer a part of base! | ||
| 137 | #else // LUA_VERSION_NUM | ||
| 138 | { LUA_COLIBNAME, nullptr }, // Lua 5.1: part of base package | ||
| 139 | #endif // LUA_VERSION_NUM | ||
| 140 | { LUA_DBLIBNAME, luaopen_debug }, | ||
| 141 | #if LUAJIT_FLAVOR() != 0 // building against LuaJIT headers, add some LuaJIT-specific libs | 145 | #if LUAJIT_FLAVOR() != 0 // building against LuaJIT headers, add some LuaJIT-specific libs |
| 142 | // #pragma message( "supporting JIT base libs") | 146 | { LUA_BITLIBNAME, luaopen_bit }, |
| 143 | { LUA_BITLIBNAME, luaopen_bit }, | 147 | { LUA_FFILIBNAME, luaopen_ffi }, |
| 144 | { LUA_JITLIBNAME, luaopen_jit }, | 148 | { LUA_JITLIBNAME, luaopen_jit }, |
| 145 | { LUA_FFILIBNAME, luaopen_ffi }, | ||
| 146 | #endif // LUAJIT_FLAVOR() | 149 | #endif // LUAJIT_FLAVOR() |
| 147 | 150 | ||
| 148 | { LUA_DBLIBNAME, luaopen_debug }, | 151 | { kLanesCoreLibName, require_lanes_core }, // So that we can open it like any base library (possible since we have access to the init function) |
| 149 | { "lanes.core", require_lanes_core }, // So that we can open it like any base library (possible since we have access to the init function) | 152 | // |
| 150 | // | 153 | { nullptr, nullptr } |
| 151 | { "base", nullptr }, // ignore "base" (already acquired it) | 154 | }; |
| 152 | { nullptr, nullptr } | 155 | |
| 153 | }; | 156 | } // namespace global |
| 154 | 157 | ||
| 155 | // ################################################################################################# | 158 | // ################################################################################################# |
| 156 | 159 | ||
| 157 | static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char const* name_, size_t len_) | 160 | static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char const* name_, size_t len_) |
| 158 | { | 161 | { |
| 159 | for (int i{ 0 }; libs[i].name; ++i) { | 162 | for (int i{ 0 }; global::sLibs[i].name; ++i) { |
| 160 | if (strncmp(name_, libs[i].name, len_) == 0) { | 163 | if (strncmp(name_, global::sLibs[i].name, len_) == 0) { |
| 161 | lua_CFunction libfunc = libs[i].func; | 164 | lua_CFunction const libfunc{ global::sLibs[i].func }; |
| 162 | name_ = libs[i].name; // note that the provided name_ doesn't necessarily ends with '\0', hence len_ | 165 | if (!libfunc) { |
| 163 | if (libfunc != nullptr) { | 166 | continue; |
| 164 | bool const isLanesCore{ libfunc == require_lanes_core }; // don't want to create a global for "lanes.core" | 167 | } |
| 165 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END(U_), (int) len_, name_)); | 168 | name_ = global::sLibs[i].name; // note that the provided name_ doesn't necessarily ends with '\0', hence len_ |
| 166 | STACK_CHECK_START_REL(L_, 0); | 169 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END(U_), (int) len_, name_)); |
| 167 | // open the library as if through require(), and create a global as well if necessary (the library table is left on the stack) | 170 | STACK_CHECK_START_REL(L_, 0); |
| 168 | luaL_requiref(L_, name_, libfunc, !isLanesCore); | 171 | // open the library as if through require(), and create a global as well if necessary (the library table is left on the stack) |
| 169 | // lanes.core doesn't declare a global, so scan it here and now | 172 | bool const isLanesCore{ libfunc == require_lanes_core }; // don't want to create a global for "lanes.core" |
| 170 | if (isLanesCore == true) { | 173 | luaL_requiref(L_, name_, libfunc, !isLanesCore); // L_: {lib} |
| 171 | populate_func_lookup_table(L_, -1, name_); | 174 | // lanes.core doesn't declare a global, so scan it here and now |
| 172 | } | 175 | if (isLanesCore) { |
| 173 | lua_pop(L_, 1); | 176 | populate_func_lookup_table(L_, -1, name_); |
| 174 | STACK_CHECK(L_, 0); | ||
| 175 | } | 177 | } |
| 178 | lua_pop(L_, 1); // L_: | ||
| 179 | STACK_CHECK(L_, 0); | ||
| 176 | break; | 180 | break; |
| 177 | } | 181 | } |
| 178 | } | 182 | } |
| @@ -180,6 +184,14 @@ static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char con | |||
| 180 | 184 | ||
| 181 | // ################################################################################################# | 185 | // ################################################################################################# |
| 182 | 186 | ||
| 187 | template<size_t N> | ||
| 188 | static inline void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char const (&name_)[N]) | ||
| 189 | { | ||
| 190 | open1lib(DEBUGSPEW_PARAM_COMMA(U_) L_, name_, N - 1); | ||
| 191 | } | ||
| 192 | |||
| 193 | // ################################################################################################# | ||
| 194 | |||
| 183 | // just like lua_xmove, args are (from, to) | 195 | // just like lua_xmove, args are (from, to) |
| 184 | static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) | 196 | static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) |
| 185 | { | 197 | { |
| @@ -195,7 +207,7 @@ static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) | |||
| 195 | // copy settings from from source to destination registry | 207 | // copy settings from from source to destination registry |
| 196 | InterCopyContext c{ U_, L2_, L1_, {}, {}, {}, {}, {} }; | 208 | InterCopyContext c{ U_, L2_, L1_, {}, {}, {}, {}, {} }; |
| 197 | if (c.inter_move(1) != InterCopyResult::Success) { // L1_: L2_: config | 209 | if (c.inter_move(1) != InterCopyResult::Success) { // L1_: L2_: config |
| 198 | raise_luaL_error(L1_, "failed to copy settings when loading lanes.core"); | 210 | raise_luaL_error(L1_, "failed to copy settings when loading " kLanesCoreLibName); |
| 199 | } | 211 | } |
| 200 | // set L2:_R[kConfigRegKey] = settings | 212 | // set L2:_R[kConfigRegKey] = settings |
| 201 | kConfigRegKey.setValue(L2_, [](lua_State* L_) { lua_insert(L_, -2); }); // L1_: L2_: config | 213 | kConfigRegKey.setValue(L2_, [](lua_State* L_) { lua_insert(L_, -2); }); // L1_: L2_: config |
| @@ -334,11 +346,10 @@ lua_State* luaG_newstate(Universe* U_, SourceState from_, char const* libs_) | |||
| 334 | // copy settings (for example because it may contain a Lua on_state_create function) | 346 | // copy settings (for example because it may contain a Lua on_state_create function) |
| 335 | copy_one_time_settings(U_, from_, L); | 347 | copy_one_time_settings(U_, from_, L); |
| 336 | 348 | ||
| 337 | // 'lua.c' stops GC during initialization so perhaps its a good idea. :) | 349 | // 'lua.c' stops GC during initialization so perhaps it is a good idea. :) |
| 338 | lua_gc(L, LUA_GCSTOP, 0); | 350 | lua_gc(L, LUA_GCSTOP, 0); |
| 339 | 351 | ||
| 340 | // Anything causes 'base' to be taken in | 352 | // Anything causes 'base' to be taken in |
| 341 | // | ||
| 342 | if (libs_ != nullptr) { | 353 | if (libs_ != nullptr) { |
| 343 | // special "*" case (mainly to help with LuaJIT compatibility) | 354 | // special "*" case (mainly to help with LuaJIT compatibility) |
| 344 | // as we are called from luaopen_lanes_core() already, and that would deadlock | 355 | // as we are called from luaopen_lanes_core() already, and that would deadlock |
| @@ -346,7 +357,7 @@ lua_State* luaG_newstate(Universe* U_, SourceState from_, char const* libs_) | |||
| 346 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening ALL standard libraries\n" INDENT_END(U_))); | 357 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening ALL standard libraries\n" INDENT_END(U_))); |
| 347 | luaL_openlibs(L); | 358 | luaL_openlibs(L); |
| 348 | // don't forget lanes.core for regular lane states | 359 | // don't forget lanes.core for regular lane states |
| 349 | open1lib(DEBUGSPEW_PARAM_COMMA(U_) L, "lanes.core", 10); | 360 | open1lib(DEBUGSPEW_PARAM_COMMA(U_) L, kLanesCoreLibName); |
| 350 | libs_ = nullptr; // done with libs | 361 | libs_ = nullptr; // done with libs |
| 351 | } else { | 362 | } else { |
| 352 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening base library\n" INDENT_END(U_))); | 363 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening base library\n" INDENT_END(U_))); |
diff --git a/src/tools.cpp b/src/tools.cpp index 2d48552..73efda9 100644 --- a/src/tools.cpp +++ b/src/tools.cpp | |||
| @@ -348,7 +348,7 @@ static void populate_func_lookup_table_recur(DEBUGSPEW_PARAM_COMMA(Universe* U_) | |||
| 348 | while (lua_next(L_, breadthFirstCache) != 0) { // L_: ... {i_} {bfc} k {} | 348 | while (lua_next(L_, breadthFirstCache) != 0) { // L_: ... {i_} {bfc} k {} |
| 349 | DEBUGSPEW_CODE(char const* key = (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostring(L_, -2) : "not a string"); | 349 | DEBUGSPEW_CODE(char const* key = (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostring(L_, -2) : "not a string"); |
| 350 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "table '%s'\n" INDENT_END(U_), key)); | 350 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "table '%s'\n" INDENT_END(U_), key)); |
| 351 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U_ }); | 351 | DEBUGSPEW_CODE(DebugSpewIndentScope scope2{ U_ }); |
| 352 | // un-visit this table in case we do need to process it | 352 | // un-visit this table in case we do need to process it |
| 353 | lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {} | 353 | lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {} |
| 354 | lua_rawget(L_, cache); // L_: ... {i_} {bfc} k {} n | 354 | lua_rawget(L_, cache); // L_: ... {i_} {bfc} k {} n |
diff --git a/src/universe.cpp b/src/universe.cpp index bf64560..6adc314 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
| @@ -1,11 +1,11 @@ | |||
| 1 | /* | 1 | /* |
| 2 | * UNIVERSE.C Copyright (c) 2017, Benoit Germain | 2 | * UNIVERSE.CPP Copyright (c) 2017-2024, Benoit Germain |
| 3 | */ | 3 | */ |
| 4 | 4 | ||
| 5 | /* | 5 | /* |
| 6 | =============================================================================== | 6 | =============================================================================== |
| 7 | 7 | ||
| 8 | Copyright (C) 2017 Benoit Germain <bnt.germain@gmail.com> | 8 | Copyright (C) 2017-2024 Benoit Germain <bnt.germain@gmail.com> |
| 9 | 9 | ||
| 10 | Permission is hereby granted, free of charge, to any person obtaining a copy | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy |
| 11 | of this software and associated documentation files (the "Software"), to deal | 11 | of this software and associated documentation files (the "Software"), to deal |
| @@ -28,18 +28,14 @@ THE SOFTWARE. | |||
| 28 | =============================================================================== | 28 | =============================================================================== |
| 29 | */ | 29 | */ |
| 30 | 30 | ||
| 31 | #include <string.h> | ||
| 32 | #include <assert.h> | ||
| 33 | |||
| 34 | #include "universe.h" | 31 | #include "universe.h" |
| 35 | #include "compat.h" | ||
| 36 | #include "macros_and_utils.h" | ||
| 37 | #include "uniquekey.h" | ||
| 38 | 32 | ||
| 39 | // xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator | 33 | #include "cancel.h" |
| 40 | static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full }; | 34 | #include "compat.h" |
| 41 | // xxh64 of string "kUniverseLightRegKey" generated at https://www.pelock.com/products/hash-calculator | 35 | #include "deep.h" |
| 42 | static constexpr RegistryUniqueKey kUniverseLightRegKey{ 0x48BBE9CEAB0BA04Full }; | 36 | #include "keeper.h" |
| 37 | #include "lanes_private.h" | ||
| 38 | #include "tools.h" | ||
| 43 | 39 | ||
| 44 | // ################################################################################################# | 40 | // ################################################################################################# |
| 45 | 41 | ||
| @@ -72,7 +68,7 @@ Universe::Universe() | |||
| 72 | // ################################################################################################# | 68 | // ################################################################################################# |
| 73 | 69 | ||
| 74 | // only called from the master state | 70 | // only called from the master state |
| 75 | Universe* universe_create(lua_State* L_) | 71 | [[nodiscard]] Universe* universe_create(lua_State* L_) |
| 76 | { | 72 | { |
| 77 | LUA_ASSERT(L_, universe_get(L_) == nullptr); | 73 | LUA_ASSERT(L_, universe_get(L_) == nullptr); |
| 78 | Universe* const U{ lua_newuserdatauv<Universe>(L_, 0) }; // universe | 74 | Universe* const U{ lua_newuserdatauv<Universe>(L_, 0) }; // universe |
| @@ -86,20 +82,94 @@ Universe* universe_create(lua_State* L_) | |||
| 86 | 82 | ||
| 87 | // ################################################################################################# | 83 | // ################################################################################################# |
| 88 | 84 | ||
| 89 | void universe_store(lua_State* L_, Universe* U_) | 85 | void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_) |
| 90 | { | 86 | { |
| 91 | LUA_ASSERT(L_, !U_ || universe_get(L_) == nullptr); | 87 | if (selfdestructFirst != SELFDESTRUCT_END) { |
| 92 | STACK_CHECK_START_REL(L_, 0); | 88 | // Signal _all_ still running threads to exit (including the timer thread) |
| 93 | kUniverseLightRegKey.setValue(L_, [U = U_](lua_State* L_) { U ? lua_pushlightuserdata(L_, U) : lua_pushnil(L_); }); | 89 | { |
| 94 | STACK_CHECK(L_, 0); | 90 | std::lock_guard<std::mutex> guard{ selfdestructMutex }; |
| 91 | Lane* lane{ selfdestructFirst }; | ||
| 92 | lua_Duration timeout{ 1us }; | ||
| 93 | while (lane != SELFDESTRUCT_END) { | ||
| 94 | // attempt the requested cancel with a small timeout. | ||
| 95 | // if waiting on a linda, they will raise a cancel_error. | ||
| 96 | // if a cancellation hook is desired, it will be installed to try to raise an error | ||
| 97 | if (lane->thread.joinable()) { | ||
| 98 | std::ignore = thread_cancel(lane, op_, 1, timeout, true); | ||
| 99 | } | ||
| 100 | lane = lane->selfdestruct_next; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | // When noticing their cancel, the lanes will remove themselves from the selfdestruct chain. | ||
| 105 | { | ||
| 106 | std::chrono::time_point<std::chrono::steady_clock> t_until{ std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(shutdownTimeout_) }; | ||
| 107 | |||
| 108 | while (selfdestructFirst != SELFDESTRUCT_END) { | ||
| 109 | // give threads time to act on their cancel | ||
| 110 | std::this_thread::yield(); | ||
| 111 | // count the number of cancelled thread that didn't have the time to act yet | ||
| 112 | int n{ 0 }; | ||
| 113 | { | ||
| 114 | std::lock_guard<std::mutex> guard{ selfdestructMutex }; | ||
| 115 | Lane* lane{ selfdestructFirst }; | ||
| 116 | while (lane != SELFDESTRUCT_END) { | ||
| 117 | if (lane->cancelRequest != CancelRequest::None) | ||
| 118 | ++n; | ||
| 119 | lane = lane->selfdestruct_next; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | // if timeout elapsed, or we know all threads have acted, stop waiting | ||
| 123 | std::chrono::time_point<std::chrono::steady_clock> t_now = std::chrono::steady_clock::now(); | ||
| 124 | if (n == 0 || (t_now >= t_until)) { | ||
| 125 | DEBUGSPEW_CODE(fprintf(stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, shutdownTimeout_.count())); | ||
| 126 | break; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | // If some lanes are currently cleaning after themselves, wait until they are done. | ||
| 132 | // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). | ||
| 133 | while (selfdestructingCount.load(std::memory_order_acquire) > 0) { | ||
| 134 | std::this_thread::yield(); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | // If after all this, we still have some free-running lanes, it's an external user error, they should have stopped appropriately | ||
| 139 | { | ||
| 140 | std::lock_guard<std::mutex> guard{ selfdestructMutex }; | ||
| 141 | Lane* lane{ selfdestructFirst }; | ||
| 142 | if (lane != SELFDESTRUCT_END) { | ||
| 143 | // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) | ||
| 144 | raise_luaL_error(L_, "Zombie thread %s refuses to die!", lane->debugName); | ||
| 145 | } | ||
| 146 | } | ||
| 95 | } | 147 | } |
| 96 | 148 | ||
| 97 | // ################################################################################################# | 149 | // ################################################################################################# |
| 98 | 150 | ||
| 99 | Universe* universe_get(lua_State* L_) | 151 | // process end: cancel any still free-running threads |
| 152 | int universe_gc(lua_State* L_) | ||
| 100 | { | 153 | { |
| 101 | STACK_CHECK_START_REL(L_, 0); | 154 | lua_Duration const shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; |
| 102 | Universe* const universe{ kUniverseLightRegKey.readLightUserDataValue<Universe>(L_) }; | 155 | [[maybe_unused]] char const* const op_string{ lua_tostring(L_, lua_upvalueindex(2)) }; |
| 103 | STACK_CHECK(L_, 0); | 156 | Universe* const U{ lua_tofulluserdata<Universe>(L_, 1) }; |
| 104 | return universe; | 157 | U->terminateFreeRunningLanes(L_, shutdown_timeout, which_cancel_op(op_string)); |
| 158 | |||
| 159 | // no need to mutex-protect this as all threads in the universe are gone at that point | ||
| 160 | if (U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer | ||
| 161 | [[maybe_unused]] int const prev_ref_count{ U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) }; | ||
| 162 | LUA_ASSERT(L_, prev_ref_count == 1); // this should be the last reference | ||
| 163 | DeepFactory::DeleteDeepObject(L_, U->timerLinda); | ||
| 164 | U->timerLinda = nullptr; | ||
| 165 | } | ||
| 166 | |||
| 167 | close_keepers(U); | ||
| 168 | |||
| 169 | // remove the protected allocator, if any | ||
| 170 | U->protectedAllocator.removeFrom(L_); | ||
| 171 | |||
| 172 | U->Universe::~Universe(); | ||
| 173 | |||
| 174 | return 0; | ||
| 105 | } | 175 | } |
diff --git a/src/universe.h b/src/universe.h index b2107af..58ddffc 100644 --- a/src/universe.h +++ b/src/universe.h | |||
| @@ -11,12 +11,14 @@ extern "C" | |||
| 11 | 11 | ||
| 12 | #include "compat.h" | 12 | #include "compat.h" |
| 13 | #include "macros_and_utils.h" | 13 | #include "macros_and_utils.h" |
| 14 | #include "uniquekey.h" | ||
| 14 | 15 | ||
| 15 | #include <mutex> | 16 | #include <mutex> |
| 16 | 17 | ||
| 17 | // ################################################################################################# | 18 | // ################################################################################################# |
| 18 | 19 | ||
| 19 | // forwards | 20 | // forwards |
| 21 | enum class CancelOp; | ||
| 20 | struct DeepPrelude; | 22 | struct DeepPrelude; |
| 21 | struct Keepers; | 23 | struct Keepers; |
| 22 | class Lane; | 24 | class Lane; |
| @@ -114,6 +116,13 @@ class ProtectedAllocator | |||
| 114 | 116 | ||
| 115 | // ################################################################################################# | 117 | // ################################################################################################# |
| 116 | 118 | ||
| 119 | // xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
| 120 | static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full }; | ||
| 121 | // xxh64 of string "kUniverseLightRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
| 122 | static constexpr RegistryUniqueKey kUniverseLightRegKey{ 0x48BBE9CEAB0BA04Full }; | ||
| 123 | |||
| 124 | // ################################################################################################# | ||
| 125 | |||
| 117 | // everything regarding the Lanes universe is stored in that global structure | 126 | // everything regarding the Lanes universe is stored in that global structure |
| 118 | // held as a full userdata in the master Lua state that required it for the first time | 127 | // held as a full userdata in the master Lua state that required it for the first time |
| 119 | class Universe | 128 | class Universe |
| @@ -154,6 +163,7 @@ class Universe | |||
| 154 | Lane* volatile trackingFirst{ nullptr }; // will change to TRACKING_END if we want to activate tracking | 163 | Lane* volatile trackingFirst{ nullptr }; // will change to TRACKING_END if we want to activate tracking |
| 155 | #endif // HAVE_LANE_TRACKING() | 164 | #endif // HAVE_LANE_TRACKING() |
| 156 | 165 | ||
| 166 | // Protects modifying the selfdestruct chain | ||
| 157 | std::mutex selfdestructMutex; | 167 | std::mutex selfdestructMutex; |
| 158 | 168 | ||
| 159 | // require() serialization | 169 | // require() serialization |
| @@ -178,6 +188,8 @@ class Universe | |||
| 178 | Universe(Universe&&) = delete; | 188 | Universe(Universe&&) = delete; |
| 179 | Universe& operator=(Universe const&) = delete; | 189 | Universe& operator=(Universe const&) = delete; |
| 180 | Universe& operator=(Universe&&) = delete; | 190 | Universe& operator=(Universe&&) = delete; |
| 191 | |||
| 192 | void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); | ||
| 181 | }; | 193 | }; |
| 182 | 194 | ||
| 183 | // ################################################################################################# | 195 | // ################################################################################################# |
| @@ -211,3 +223,27 @@ class DebugSpewIndentScope | |||
| 211 | } | 223 | } |
| 212 | }; | 224 | }; |
| 213 | #endif // USE_DEBUG_SPEW() | 225 | #endif // USE_DEBUG_SPEW() |
| 226 | |||
| 227 | // ################################################################################################# | ||
| 228 | |||
| 229 | [[nodiscard]] inline Universe* universe_get(lua_State* L_) | ||
| 230 | { | ||
| 231 | STACK_CHECK_START_REL(L_, 0); | ||
| 232 | Universe* const universe{ kUniverseLightRegKey.readLightUserDataValue<Universe>(L_) }; | ||
| 233 | STACK_CHECK(L_, 0); | ||
| 234 | return universe; | ||
| 235 | } | ||
| 236 | |||
| 237 | // ################################################################################################# | ||
| 238 | |||
| 239 | inline void universe_store(lua_State* L_, Universe* U_) | ||
| 240 | { | ||
| 241 | LUA_ASSERT(L_, !U_ || universe_get(L_) == nullptr); | ||
| 242 | STACK_CHECK_START_REL(L_, 0); | ||
| 243 | kUniverseLightRegKey.setValue(L_, [U = U_](lua_State* L_) { U ? lua_pushlightuserdata(L_, U) : lua_pushnil(L_); }); | ||
| 244 | STACK_CHECK(L_, 0); | ||
| 245 | } | ||
| 246 | |||
| 247 | // ################################################################################################# | ||
| 248 | |||
| 249 | int universe_gc(lua_State* L_); | ||
