From a57690123ae3ce5bdd7e970690f1380e88e4eaf6 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 17 Mar 2025 12:34:08 +0100 Subject: Raise a regular Lua error instead of throwing a C++ std::logic_error exception in Universe::UniverseGC --- src/lane.cpp | 6 ++++++ src/lane.hpp | 4 ++++ src/universe.cpp | 46 ++++++++++++++++++++++++++++++++++++---------- src/universe.hpp | 3 ++- 4 files changed, 48 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/lane.cpp b/src/lane.cpp index 7a5c257..5cebdfa 100644 --- a/src/lane.cpp +++ b/src/lane.cpp @@ -756,6 +756,12 @@ static void lane_main(Lane* const lane_) } } + if (lane_->flaggedAfterUniverseGC.load(std::memory_order_relaxed)) { + // let's try not to crash if the lane didn't terminate gracefully and the Universe met its end + // there will be leaks, but what else can we do? + return; + } + if (_errorHandlerCount) { lua_remove(_L, 1); // L: retvals|error } diff --git a/src/lane.hpp b/src/lane.hpp index 9b678d6..5fe36b6 100644 --- a/src/lane.hpp +++ b/src/lane.hpp @@ -139,6 +139,10 @@ class Lane final ErrorTraceLevel const errorTraceLevel{ Basic }; + // when Universe is collected, and an uncooperative Lane refuses to terminate, this flag becomes true + // in case of crash, that's the Lane's fault! + std::atomic_bool flaggedAfterUniverseGC{ false }; + [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception diff --git a/src/universe.cpp b/src/universe.cpp index 3da0801..dd7bc4b 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -209,6 +209,17 @@ static int luaG_provide_protected_allocator(lua_State* const L_) // ################################################################################################# +void Universe::flagDanglingLanes() const +{ + std::lock_guard _guard{ selfdestructMutex }; + Lane* _lane{ selfdestructFirst }; + while (_lane != SELFDESTRUCT_END) { + _lane->flaggedAfterUniverseGC.store(true, std::memory_order_relaxed); + _lane = _lane->selfdestruct_next; + } +} +// ################################################################################################# + // called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) // Do I need to disable this when compiling for LuaJIT to prevent issues? void Universe::initializeAllocatorFunction(lua_State* const L_) @@ -399,6 +410,7 @@ bool Universe::terminateFreeRunningLanes(lua_Duration const shutdownTimeout_, Ca // ################################################################################################# // process end: cancel any still free-running threads +// as far as I can tell, this can only by called only from lua_close() int Universe::UniverseGC(lua_State* const L_) { lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; @@ -416,23 +428,37 @@ int Universe::UniverseGC(lua_State* const L_) kFinalizerRegKey.pushValue(L_); // L_: U finalizer|nil if (!lua_isnil(L_, -1)) { lua_pushboolean(L_, _allLanesTerminated); // L_: U finalizer bool - // no protection. Lua rules for errors in finalizers apply normally - lua_call(L_, 1, 1); // L_: U ret|error + // no protection. Lua rules for errors in finalizers apply normally: + // Lua 5.4: error is propagated in the warn system + // older: error is swallowed + lua_call(L_, 1, 1); // L_: U msg? + // phew, no error in finalizer, since we reached that point } - STACK_CHECK(L_, 2); - // if some lanes are still running here, we have no other choice than crashing or freezing and let the client figure out what's wrong - bool const _throw{ luaG_tostring(L_, kIdxTop) == "throw" }; - lua_pop(L_, 1); // L_: U + if (lua_isnil(L_, kIdxTop)) { + lua_pop(L_, 1); // L_: U + // no finalizer, or it returned no value: push some default message on the stack, in case it is necessary + luaG_pushstring(L_, "uncooperative lanes detected at shutdown"); // L_: U "msg" + } + STACK_CHECK(L_, 2); - while (_U->selfdestructFirst != SELFDESTRUCT_END) { - if (_throw) { - throw std::logic_error{ "Some lanes are still running at shutdown" }; + // now, all remaining lanes are flagged. if they crash because we remove keepers and the Universe from under them, it is their fault + bool const _detectedUncooperativeLanes{ _U->selfdestructFirst != SELFDESTRUCT_END }; + if (_detectedUncooperativeLanes) { + _U->flagDanglingLanes(); + if (luaG_tostring(L_, kIdxTop) == "freeze") { + std::this_thread::sleep_until(std::chrono::time_point::max()); } else { - std::this_thread::yield(); + // take the value returned by the finalizer (or our default message) and throw it as an error + // since we are inside Lua's GCTM, it will be propagated through the warning system (Lua 5.4) or swallowed silently + raise_lua_error(L_); } } + // --------------------------------------------------------- + // we don't reach that point if some lanes are still running + // --------------------------------------------------------- + // no need to mutex-protect this as all lanes in the universe are gone at that point Linda::DeleteTimerLinda(L_, std::exchange(_U->timerLinda, nullptr), PK); diff --git a/src/universe.hpp b/src/universe.hpp index ab06907..2a3085d 100644 --- a/src/universe.hpp +++ b/src/universe.hpp @@ -106,7 +106,7 @@ class Universe final LaneTracker tracker; // Protects modifying the selfdestruct chain - std::mutex selfdestructMutex; + mutable std::mutex selfdestructMutex; // require() serialization std::recursive_mutex requireMutex; @@ -126,6 +126,7 @@ class Universe final private: static int UniverseGC(lua_State* L_); + void flagDanglingLanes() const; public: [[nodiscard]] -- cgit v1.2.3-55-g6feb