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_); | ||