From d093c5555ec439affcfbecdceabfb122aa8c2f73 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 7 May 2024 09:42:09 +0200 Subject: Some more code refactorization --- src/compat.cpp | 2 +- src/compat.h | 2 + src/lanes.cpp | 135 +++++++-------------------------------------------- src/lanes_private.h | 10 +++- src/lindafactory.cpp | 5 +- src/state.cpp | 107 ++++++++++++++++++++++------------------ src/tools.cpp | 2 +- src/universe.cpp | 116 ++++++++++++++++++++++++++++++++++--------- src/universe.h | 36 ++++++++++++++ 9 files changed, 223 insertions(+), 192 deletions(-) (limited to 'src') 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_) // ################################################################################################# // Copied from Lua 5.2 loadlib.c -static int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_) +int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_) { lua_getfield(L_, idx_, fname_); 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 } #define LUA_LOADED_TABLE "_LOADED" // // doesn't exist in Lua 5.1 +int luaL_getsubtable(lua_State* L_, int idx_, const char* fname_); + #endif // LUA_VERSION_NUM == 501 // ################################################################################################# 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_) // ########################################### Threads ############################################# // ################################################################################################# -// -// Protects modifying the selfdestruct chain - -#define SELFDESTRUCT_END ((Lane*) (-1)) -// -// The chain is ended by '(Lane*)(-1)', not nullptr: -// 'selfdestructFirst -> ... -> ... -> (-1)' - /* * Add the lane to selfdestruct chain; the ones still running at the end of the * whole process will be cancelled. @@ -446,99 +438,6 @@ static void selfdestruct_add(Lane* lane_) // ################################################################################################# -// process end: cancel any still free-running threads -[[nodiscard]] static int universe_gc(lua_State* L_) -{ - Universe* const U{ lua_tofulluserdata(L_, 1) }; - lua_Duration const shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; - [[maybe_unused]] char const* const op_string{ lua_tostring(L_, lua_upvalueindex(2)) }; - CancelOp const op{ which_cancel_op(op_string) }; - - if (U->selfdestructFirst != SELFDESTRUCT_END) { - // Signal _all_ still running threads to exit (including the timer thread) - { - std::lock_guard guard{ U->selfdestructMutex }; - Lane* lane{ U->selfdestructFirst }; - lua_Duration timeout{ 1us }; - while (lane != SELFDESTRUCT_END) { - // attempt the requested cancel with a small timeout. - // if waiting on a linda, they will raise a cancel_error. - // if a cancellation hook is desired, it will be installed to try to raise an error - if (lane->thread.joinable()) { - std::ignore = thread_cancel(lane, op, 1, timeout, true); - } - lane = lane->selfdestruct_next; - } - } - - // When noticing their cancel, the lanes will remove themselves from the selfdestruct chain. - { - std::chrono::time_point t_until{ std::chrono::steady_clock::now() + std::chrono::duration_cast(shutdown_timeout) }; - - while (U->selfdestructFirst != SELFDESTRUCT_END) { - // give threads time to act on their cancel - std::this_thread::yield(); - // count the number of cancelled thread that didn't have the time to act yet - int n{ 0 }; - { - std::lock_guard guard{ U->selfdestructMutex }; - Lane* lane{ U->selfdestructFirst }; - while (lane != SELFDESTRUCT_END) { - if (lane->cancelRequest != CancelRequest::None) - ++n; - lane = lane->selfdestruct_next; - } - } - // if timeout elapsed, or we know all threads have acted, stop waiting - std::chrono::time_point t_now = std::chrono::steady_clock::now(); - if (n == 0 || (t_now >= t_until)) { - DEBUGSPEW_CODE(fprintf(stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, shutdown_timeout.count())); - break; - } - } - } - - // If some lanes are currently cleaning after themselves, wait until they are done. - // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). - while (U->selfdestructingCount.load(std::memory_order_acquire) > 0) { - std::this_thread::yield(); - } - } - - // If after all this, we still have some free-running lanes, it's an external user error, they should have stopped appropriately - { - std::lock_guard guard{ U->selfdestructMutex }; - Lane* lane{ U->selfdestructFirst }; - if (lane != SELFDESTRUCT_END) { - // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) - raise_luaL_error(L_, "Zombie thread %s refuses to die!", lane->debugName); - } - } - - // no need to mutex-protect this as all threads in the universe are gone at that point - if (U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer - [[maybe_unused]] int const prev_ref_count{ U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) }; - LUA_ASSERT(L_, prev_ref_count == 1); // this should be the last reference - DeepFactory::DeleteDeepObject(L_, U->timerLinda); - U->timerLinda = nullptr; - } - - close_keepers(U); - - // remove the protected allocator, if any - U->protectedAllocator.removeFrom(L_); - - U->Universe::~Universe(); - - // universe is no longer available (nor necessary) - // we need to do this in case some deep userdata objects were created before Lanes was initialized, - // as potentially they will be garbage collected after Lanes at application shutdown - universe_store(L_, nullptr); - return 0; -} - -// ################################################################################################# - //--- // = _single( [cores_uint=1] ) // @@ -1526,14 +1425,13 @@ LUAG_FUNC(threads) Universe* const U{ universe_get(L_) }; // List _all_ still running threads - // std::lock_guard guard{ U->trackingMutex }; if (U->trackingFirst && U->trackingFirst != TRACKING_END) { Lane* lane{ U->trackingFirst }; int index{ 0 }; lua_newtable(L_); // L_: {} while (lane != TRACKING_END) { - // insert a { name, status } tuple, so that several lanes with the same name can't clobber each other + // insert a { name='', status='' } tuple, so that several lanes with the same name can't clobber each other lua_createtable(L_, 0, 2); // L_: {} {} lua_pushstring(L_, lane->debugName); // L_: {} {} "name" lua_setfield(L_, -2, "name"); // L_: {} {} @@ -1622,17 +1520,20 @@ LUAG_FUNC(wakeup_conv) // ################################################################################################# extern int LG_linda(lua_State* L_); -static struct luaL_Reg const lanes_functions[] = { - { "linda", LG_linda }, - { "now_secs", LG_now_secs }, - { "wakeup_conv", LG_wakeup_conv }, - { "set_thread_priority", LG_set_thread_priority }, - { "set_thread_affinity", LG_set_thread_affinity }, - { "nameof", luaG_nameof }, - { "register", LG_register }, - { "set_singlethreaded", LG_set_singlethreaded }, - { nullptr, nullptr } -}; + +namespace global { + static struct luaL_Reg const sLanesFunctions[] = { + { "linda", LG_linda }, + { "now_secs", LG_now_secs }, + { "wakeup_conv", LG_wakeup_conv }, + { "set_thread_priority", LG_set_thread_priority }, + { "set_thread_affinity", LG_set_thread_affinity }, + { "nameof", luaG_nameof }, + { "register", LG_register }, + { "set_singlethreaded", LG_set_singlethreaded }, + { nullptr, nullptr } + }; +} // namespace global // ################################################################################################# @@ -1715,7 +1616,7 @@ LUAG_FUNC(configure) lua_pushnil(L_); // L_: settings M nil lua_setfield(L_, -2, "configure"); // L_: settings M // add functions to the module's table - luaG_registerlibfuncs(L_, lanes_functions); + luaG_registerlibfuncs(L_, global::sLanesFunctions); #if HAVE_LANE_TRACKING() // register core.threads() only if settings say it should be available if (U->trackingFirst != nullptr) { @@ -1739,7 +1640,7 @@ LUAG_FUNC(configure) // prepare the metatable for threads // contains keys: { __gc, __index, cached_error, cached_tostring, cancel, join, get_debug_threadname } // - if (luaL_newmetatable(L_, "Lane")) { // L_: settings M mt + if (luaL_newmetatable(L_, kLaneMetatableName)) { // L_: settings M mt lua_pushcfunction(L_, lane_gc); // L_: settings M mt lane_gc lua_setfield(L_, -2, "__gc"); // L_: settings M mt lua_pushcfunction(L_, LG_thread_index); // L_: settings M mt LG_thread_index @@ -1756,7 +1657,7 @@ LUAG_FUNC(configure) lua_setfield(L_, -2, "get_debug_threadname"); // L_: settings M mt lua_pushcfunction(L_, LG_thread_cancel); // L_: settings M mt LG_thread_cancel lua_setfield(L_, -2, "cancel"); // L_: settings M mt - lua_pushliteral(L_, "Lane"); // L_: settings M mt "Lane" + lua_pushliteral(L_, kLaneMetatableName); // L_: settings M mt "Lane" lua_setfield(L_, -2, "__metatable"); // L_: settings M mt } 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 @@ #include #include +// The chain is ended by '(Lane*)(-1)', not nullptr: 'selfdestructFirst -> ... -> ... -> (-1)' +#define SELFDESTRUCT_END ((Lane*) (-1)) + +// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) +#define kLaneMetatableName "Lane" +#define kLanesLibName "lanes" +#define kLanesCoreLibName kLanesLibName ".core" + // NOTE: values to be changed by either thread, during execution, without // locking, are marked "volatile" // @@ -102,5 +110,5 @@ static constexpr RegistryUniqueKey kLanePointerRegKey{ 0x2D8CF03FE9F0A51Aull }; // [[nodiscard]] inline Lane* ToLane(lua_State* L_, int i_) { - return *(static_cast(luaL_checkudata(L_, i_, "Lane"))); + return *(static_cast(luaL_checkudata(L_, i_, kLaneMetatableName))); } 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. #include "linda.h" +// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) +#define kLindaMetatableName "Linda" + // ################################################################################################# void LindaFactory::createMetatable(lua_State* L_) const @@ -45,7 +48,7 @@ void LindaFactory::createMetatable(lua_State* L_) const lua_setfield(L_, -2, "__index"); // protect metatable from external access - lua_pushliteral(L_, "Linda"); + lua_pushliteral(L_, kLindaMetatableName); lua_setfield(L_, -2, "__metatable"); // 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. #include "state.h" #include "lanes.h" +#include "lanes_private.h" #include "tools.h" #include "universe.h" @@ -111,68 +112,71 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_) [[nodiscard]] static int require_lanes_core(lua_State* L_) { // leaves a copy of 'lanes.core' module table on the stack - luaL_requiref(L_, "lanes.core", luaopen_lanes_core, 0); + luaL_requiref(L_, kLanesCoreLibName, luaopen_lanes_core, 0); return 1; } // ################################################################################################# -static luaL_Reg const libs[] = { - { LUA_LOADLIBNAME, luaopen_package }, - { LUA_TABLIBNAME, luaopen_table }, - { LUA_STRLIBNAME, luaopen_string }, - { LUA_MATHLIBNAME, luaopen_math }, +namespace global +{ + static luaL_Reg const sLibs[] = { + { "base", nullptr }, // ignore "base" (already acquired it) +#if LUA_VERSION_NUM >= 502 +#ifdef luaopen_bit32 + { LUA_BITLIBNAME, luaopen_bit32 }, +#endif + { LUA_COLIBNAME, luaopen_coroutine }, // Lua 5.2: coroutine is no longer a part of base! +#else // LUA_VERSION_NUM + { LUA_COLIBNAME, nullptr }, // Lua 5.1: part of base package +#endif // LUA_VERSION_NUM + { LUA_DBLIBNAME, luaopen_debug }, #ifndef PLATFORM_XBOX // no os/io libs on xbox - { LUA_OSLIBNAME, luaopen_os }, - { LUA_IOLIBNAME, luaopen_io }, + { LUA_IOLIBNAME, luaopen_io }, + { LUA_OSLIBNAME, luaopen_os }, #endif // PLATFORM_XBOX + { LUA_LOADLIBNAME, luaopen_package }, + { LUA_MATHLIBNAME, luaopen_math }, + { LUA_STRLIBNAME, luaopen_string }, + { LUA_TABLIBNAME, luaopen_table }, #if LUA_VERSION_NUM >= 503 - { LUA_UTF8LIBNAME, luaopen_utf8 }, + { LUA_UTF8LIBNAME, luaopen_utf8 }, #endif -#if LUA_VERSION_NUM >= 502 -#ifdef luaopen_bit32 - { LUA_BITLIBNAME, luaopen_bit32 }, -#endif - { LUA_COLIBNAME, luaopen_coroutine }, // Lua 5.2: coroutine is no longer a part of base! -#else // LUA_VERSION_NUM - { LUA_COLIBNAME, nullptr }, // Lua 5.1: part of base package -#endif // LUA_VERSION_NUM - { LUA_DBLIBNAME, luaopen_debug }, #if LUAJIT_FLAVOR() != 0 // building against LuaJIT headers, add some LuaJIT-specific libs - // #pragma message( "supporting JIT base libs") - { LUA_BITLIBNAME, luaopen_bit }, - { LUA_JITLIBNAME, luaopen_jit }, - { LUA_FFILIBNAME, luaopen_ffi }, + { LUA_BITLIBNAME, luaopen_bit }, + { LUA_FFILIBNAME, luaopen_ffi }, + { LUA_JITLIBNAME, luaopen_jit }, #endif // LUAJIT_FLAVOR() - { LUA_DBLIBNAME, luaopen_debug }, - { "lanes.core", require_lanes_core }, // So that we can open it like any base library (possible since we have access to the init function) - // - { "base", nullptr }, // ignore "base" (already acquired it) - { nullptr, nullptr } -}; + { kLanesCoreLibName, require_lanes_core }, // So that we can open it like any base library (possible since we have access to the init function) + // + { nullptr, nullptr } + }; + +} // namespace global // ################################################################################################# static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char const* name_, size_t len_) { - for (int i{ 0 }; libs[i].name; ++i) { - if (strncmp(name_, libs[i].name, len_) == 0) { - lua_CFunction libfunc = libs[i].func; - name_ = libs[i].name; // note that the provided name_ doesn't necessarily ends with '\0', hence len_ - if (libfunc != nullptr) { - bool const isLanesCore{ libfunc == require_lanes_core }; // don't want to create a global for "lanes.core" - DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END(U_), (int) len_, name_)); - STACK_CHECK_START_REL(L_, 0); - // open the library as if through require(), and create a global as well if necessary (the library table is left on the stack) - luaL_requiref(L_, name_, libfunc, !isLanesCore); - // lanes.core doesn't declare a global, so scan it here and now - if (isLanesCore == true) { - populate_func_lookup_table(L_, -1, name_); - } - lua_pop(L_, 1); - STACK_CHECK(L_, 0); + for (int i{ 0 }; global::sLibs[i].name; ++i) { + if (strncmp(name_, global::sLibs[i].name, len_) == 0) { + lua_CFunction const libfunc{ global::sLibs[i].func }; + if (!libfunc) { + continue; + } + name_ = global::sLibs[i].name; // note that the provided name_ doesn't necessarily ends with '\0', hence len_ + DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END(U_), (int) len_, name_)); + STACK_CHECK_START_REL(L_, 0); + // open the library as if through require(), and create a global as well if necessary (the library table is left on the stack) + bool const isLanesCore{ libfunc == require_lanes_core }; // don't want to create a global for "lanes.core" + luaL_requiref(L_, name_, libfunc, !isLanesCore); // L_: {lib} + // lanes.core doesn't declare a global, so scan it here and now + if (isLanesCore) { + populate_func_lookup_table(L_, -1, name_); } + lua_pop(L_, 1); // L_: + STACK_CHECK(L_, 0); break; } } @@ -180,6 +184,14 @@ static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char con // ################################################################################################# +template +static inline void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_, char const (&name_)[N]) +{ + open1lib(DEBUGSPEW_PARAM_COMMA(U_) L_, name_, N - 1); +} + +// ################################################################################################# + // just like lua_xmove, args are (from, to) static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) { @@ -195,7 +207,7 @@ static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) // copy settings from from source to destination registry InterCopyContext c{ U_, L2_, L1_, {}, {}, {}, {}, {} }; if (c.inter_move(1) != InterCopyResult::Success) { // L1_: L2_: config - raise_luaL_error(L1_, "failed to copy settings when loading lanes.core"); + raise_luaL_error(L1_, "failed to copy settings when loading " kLanesCoreLibName); } // set L2:_R[kConfigRegKey] = settings 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_) // copy settings (for example because it may contain a Lua on_state_create function) copy_one_time_settings(U_, from_, L); - // 'lua.c' stops GC during initialization so perhaps its a good idea. :) + // 'lua.c' stops GC during initialization so perhaps it is a good idea. :) lua_gc(L, LUA_GCSTOP, 0); // Anything causes 'base' to be taken in - // if (libs_ != nullptr) { // special "*" case (mainly to help with LuaJIT compatibility) // 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_) DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "opening ALL standard libraries\n" INDENT_END(U_))); luaL_openlibs(L); // don't forget lanes.core for regular lane states - open1lib(DEBUGSPEW_PARAM_COMMA(U_) L, "lanes.core", 10); + open1lib(DEBUGSPEW_PARAM_COMMA(U_) L, kLanesCoreLibName); libs_ = nullptr; // done with libs } else { 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_) while (lua_next(L_, breadthFirstCache) != 0) { // L_: ... {i_} {bfc} k {} DEBUGSPEW_CODE(char const* key = (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostring(L_, -2) : "not a string"); DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "table '%s'\n" INDENT_END(U_), key)); - DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U_ }); + DEBUGSPEW_CODE(DebugSpewIndentScope scope2{ U_ }); // un-visit this table in case we do need to process it lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {} 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 @@ /* - * UNIVERSE.C Copyright (c) 2017, Benoit Germain + * UNIVERSE.CPP Copyright (c) 2017-2024, Benoit Germain */ /* =============================================================================== -Copyright (C) 2017 Benoit Germain +Copyright (C) 2017-2024 Benoit Germain Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28,18 +28,14 @@ THE SOFTWARE. =============================================================================== */ -#include -#include - #include "universe.h" -#include "compat.h" -#include "macros_and_utils.h" -#include "uniquekey.h" -// xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator -static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full }; -// xxh64 of string "kUniverseLightRegKey" generated at https://www.pelock.com/products/hash-calculator -static constexpr RegistryUniqueKey kUniverseLightRegKey{ 0x48BBE9CEAB0BA04Full }; +#include "cancel.h" +#include "compat.h" +#include "deep.h" +#include "keeper.h" +#include "lanes_private.h" +#include "tools.h" // ################################################################################################# @@ -72,7 +68,7 @@ Universe::Universe() // ################################################################################################# // only called from the master state -Universe* universe_create(lua_State* L_) +[[nodiscard]] Universe* universe_create(lua_State* L_) { LUA_ASSERT(L_, universe_get(L_) == nullptr); Universe* const U{ lua_newuserdatauv(L_, 0) }; // universe @@ -86,20 +82,94 @@ Universe* universe_create(lua_State* L_) // ################################################################################################# -void universe_store(lua_State* L_, Universe* U_) +void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_) { - LUA_ASSERT(L_, !U_ || universe_get(L_) == nullptr); - STACK_CHECK_START_REL(L_, 0); - kUniverseLightRegKey.setValue(L_, [U = U_](lua_State* L_) { U ? lua_pushlightuserdata(L_, U) : lua_pushnil(L_); }); - STACK_CHECK(L_, 0); + if (selfdestructFirst != SELFDESTRUCT_END) { + // Signal _all_ still running threads to exit (including the timer thread) + { + std::lock_guard guard{ selfdestructMutex }; + Lane* lane{ selfdestructFirst }; + lua_Duration timeout{ 1us }; + while (lane != SELFDESTRUCT_END) { + // attempt the requested cancel with a small timeout. + // if waiting on a linda, they will raise a cancel_error. + // if a cancellation hook is desired, it will be installed to try to raise an error + if (lane->thread.joinable()) { + std::ignore = thread_cancel(lane, op_, 1, timeout, true); + } + lane = lane->selfdestruct_next; + } + } + + // When noticing their cancel, the lanes will remove themselves from the selfdestruct chain. + { + std::chrono::time_point t_until{ std::chrono::steady_clock::now() + std::chrono::duration_cast(shutdownTimeout_) }; + + while (selfdestructFirst != SELFDESTRUCT_END) { + // give threads time to act on their cancel + std::this_thread::yield(); + // count the number of cancelled thread that didn't have the time to act yet + int n{ 0 }; + { + std::lock_guard guard{ selfdestructMutex }; + Lane* lane{ selfdestructFirst }; + while (lane != SELFDESTRUCT_END) { + if (lane->cancelRequest != CancelRequest::None) + ++n; + lane = lane->selfdestruct_next; + } + } + // if timeout elapsed, or we know all threads have acted, stop waiting + std::chrono::time_point t_now = std::chrono::steady_clock::now(); + if (n == 0 || (t_now >= t_until)) { + DEBUGSPEW_CODE(fprintf(stderr, "%d uncancelled lane(s) remain after waiting %fs at process end.\n", n, shutdownTimeout_.count())); + break; + } + } + } + + // If some lanes are currently cleaning after themselves, wait until they are done. + // They are no longer listed in the selfdestruct chain, but they still have to lua_close(). + while (selfdestructingCount.load(std::memory_order_acquire) > 0) { + std::this_thread::yield(); + } + } + + // If after all this, we still have some free-running lanes, it's an external user error, they should have stopped appropriately + { + std::lock_guard guard{ selfdestructMutex }; + Lane* lane{ selfdestructFirst }; + if (lane != SELFDESTRUCT_END) { + // this causes a leak because we don't call U's destructor (which could be bad if the still running lanes are accessing it) + raise_luaL_error(L_, "Zombie thread %s refuses to die!", lane->debugName); + } + } } // ################################################################################################# -Universe* universe_get(lua_State* L_) +// process end: cancel any still free-running threads +int universe_gc(lua_State* L_) { - STACK_CHECK_START_REL(L_, 0); - Universe* const universe{ kUniverseLightRegKey.readLightUserDataValue(L_) }; - STACK_CHECK(L_, 0); - return universe; + lua_Duration const shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; + [[maybe_unused]] char const* const op_string{ lua_tostring(L_, lua_upvalueindex(2)) }; + Universe* const U{ lua_tofulluserdata(L_, 1) }; + U->terminateFreeRunningLanes(L_, shutdown_timeout, which_cancel_op(op_string)); + + // no need to mutex-protect this as all threads in the universe are gone at that point + if (U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer + [[maybe_unused]] int const prev_ref_count{ U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) }; + LUA_ASSERT(L_, prev_ref_count == 1); // this should be the last reference + DeepFactory::DeleteDeepObject(L_, U->timerLinda); + U->timerLinda = nullptr; + } + + close_keepers(U); + + // remove the protected allocator, if any + U->protectedAllocator.removeFrom(L_); + + U->Universe::~Universe(); + + return 0; } 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" #include "compat.h" #include "macros_and_utils.h" +#include "uniquekey.h" #include // ################################################################################################# // forwards +enum class CancelOp; struct DeepPrelude; struct Keepers; class Lane; @@ -114,6 +116,13 @@ class ProtectedAllocator // ################################################################################################# +// xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator +static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full }; +// xxh64 of string "kUniverseLightRegKey" generated at https://www.pelock.com/products/hash-calculator +static constexpr RegistryUniqueKey kUniverseLightRegKey{ 0x48BBE9CEAB0BA04Full }; + +// ################################################################################################# + // everything regarding the Lanes universe is stored in that global structure // held as a full userdata in the master Lua state that required it for the first time class Universe @@ -154,6 +163,7 @@ class Universe Lane* volatile trackingFirst{ nullptr }; // will change to TRACKING_END if we want to activate tracking #endif // HAVE_LANE_TRACKING() + // Protects modifying the selfdestruct chain std::mutex selfdestructMutex; // require() serialization @@ -178,6 +188,8 @@ class Universe Universe(Universe&&) = delete; Universe& operator=(Universe const&) = delete; Universe& operator=(Universe&&) = delete; + + void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); }; // ################################################################################################# @@ -211,3 +223,27 @@ class DebugSpewIndentScope } }; #endif // USE_DEBUG_SPEW() + +// ################################################################################################# + +[[nodiscard]] inline Universe* universe_get(lua_State* L_) +{ + STACK_CHECK_START_REL(L_, 0); + Universe* const universe{ kUniverseLightRegKey.readLightUserDataValue(L_) }; + STACK_CHECK(L_, 0); + return universe; +} + +// ################################################################################################# + +inline void universe_store(lua_State* L_, Universe* U_) +{ + LUA_ASSERT(L_, !U_ || universe_get(L_) == nullptr); + STACK_CHECK_START_REL(L_, 0); + kUniverseLightRegKey.setValue(L_, [U = U_](lua_State* L_) { U ? lua_pushlightuserdata(L_, U) : lua_pushnil(L_); }); + STACK_CHECK(L_, 0); +} + +// ################################################################################################# + +int universe_gc(lua_State* L_); -- cgit v1.2.3-55-g6feb