aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-07 09:42:09 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-13 18:15:46 +0200
commitd093c5555ec439affcfbecdceabfb122aa8c2f73 (patch)
treede148388b0382d87bcc2caed72625e3dec829bda /src
parentebb0837588336e32fc1258a3838673afdd31ee71 (diff)
downloadlanes-d093c5555ec439affcfbecdceabfb122aa8c2f73.tar.gz
lanes-d093c5555ec439affcfbecdceabfb122aa8c2f73.tar.bz2
lanes-d093c5555ec439affcfbecdceabfb122aa8c2f73.zip
Some more code refactorization
Diffstat (limited to 'src')
-rw-r--r--src/compat.cpp2
-rw-r--r--src/compat.h2
-rw-r--r--src/lanes.cpp135
-rw-r--r--src/lanes_private.h10
-rw-r--r--src/lindafactory.cpp5
-rw-r--r--src/state.cpp107
-rw-r--r--src/tools.cpp2
-rw-r--r--src/universe.cpp116
-rw-r--r--src/universe.h36
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
32static int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_) 32int 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
71int 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
1624extern int LG_linda(lua_State* L_); 1522extern int LG_linda(lua_State* L_);
1625static struct luaL_Reg const lanes_functions[] = { 1523
1626 { "linda", LG_linda }, 1524namespace 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
39void LindaFactory::createMetatable(lua_State* L_) const 42void 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
120static luaL_Reg const libs[] = { 121namespace 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
157static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char const* name_, size_t len_) 160static 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
187template<size_t N>
188static 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)
184static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) 196static 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
8Copyright (C) 2017 Benoit Germain <bnt.germain@gmail.com> 8Copyright (C) 2017-2024 Benoit Germain <bnt.germain@gmail.com>
9 9
10Permission is hereby granted, free of charge, to any person obtaining a copy 10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal 11of 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"
40static 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"
42static 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
75Universe* 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
89void universe_store(lua_State* L_, Universe* U_) 85void 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
99Universe* universe_get(lua_State* L_) 151// process end: cancel any still free-running threads
152int 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
21enum class CancelOp;
20struct DeepPrelude; 22struct DeepPrelude;
21struct Keepers; 23struct Keepers;
22class Lane; 24class 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
120static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full };
121// xxh64 of string "kUniverseLightRegKey" generated at https://www.pelock.com/products/hash-calculator
122static 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
119class Universe 128class 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
239inline 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
249int universe_gc(lua_State* L_);