aboutsummaryrefslogtreecommitdiff
path: root/src/universe.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/universe.cpp137
1 files changed, 96 insertions, 41 deletions
diff --git a/src/universe.cpp b/src/universe.cpp
index 3da0801..4db036b 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -51,6 +51,16 @@ static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full };
51 51
52// ################################################################################################# 52// #################################################################################################
53 53
54[[nodiscard]]
55void* ProtectedAllocator::Protected_lua_Alloc(void* const ud_, void* const ptr_, size_t const osize_, size_t const nsize_)
56{
57 ProtectedAllocator* const _allocator{ static_cast<ProtectedAllocator*>(ud_) };
58 std::lock_guard<std::mutex> _guard{ _allocator->mutex };
59 return _allocator->alloc(ptr_, osize_, nsize_);
60}
61
62// #################################################################################################
63
54Universe::Universe() 64Universe::Universe()
55{ 65{
56 //--- 66 //---
@@ -101,18 +111,18 @@ void Universe::callOnStateCreate(lua_State* const L_, lua_State* const from_, Lo
101 } 111 }
102 kConfigRegKey.pushValue(L_); // L_: config 112 kConfigRegKey.pushValue(L_); // L_: config
103 STACK_CHECK(L_, 1); 113 STACK_CHECK(L_, 1);
104 LuaType const _funcType{ luaG_getfield(L_, kIdxTop, kOnStateCreate) }; // L_: config on_state_create() 114 LuaType const _funcType{ luaW_getfield(L_, kIdxTop, kOnStateCreate) }; // L_: config on_state_create()
105 if (_funcType != LuaType::FUNCTION) { 115 if (_funcType != LuaType::FUNCTION) {
106 raise_luaL_error(L_, "INTERNAL ERROR: %s is a %s, not a function", kOnStateCreate.data(), luaG_typename(L_, _funcType).data()); 116 raise_luaL_error(L_, "INTERNAL ERROR: %s is a %s, not a function", kOnStateCreate.data(), luaW_typename(L_, _funcType).data());
107 } 117 }
108 lua_remove(L_, -2); // L_: on_state_create() 118 lua_remove(L_, -2); // L_: on_state_create()
109 } 119 }
110 STACK_CHECK(L_, 1); 120 STACK_CHECK(L_, 1);
111 // capture error and raise it in caller state 121 // capture error and raise it in caller state
112 std::string_view const _stateType{ mode_ == LookupMode::LaneBody ? "lane" : "keeper" }; 122 std::string_view const _stateType{ mode_ == LookupMode::LaneBody ? "lane" : "keeper" };
113 luaG_pushstring(L_, _stateType); // L_: on_state_create() "<type>" 123 luaW_pushstring(L_, _stateType); // L_: on_state_create() "<type>"
114 if (lua_pcall(L_, 1, 0, 0) != LUA_OK) { 124 if (lua_pcall(L_, 1, 0, 0) != LUA_OK) {
115 raise_luaL_error(from_, "%s failed in %s: \"%s\"", kOnStateCreate.data(), _stateType.data(), lua_isstring(L_, -1) ? luaG_tostring(L_, kIdxTop).data() : luaG_typename(L_, kIdxTop).data()); 125 raise_luaL_error(from_, "%s failed in %s: \"%s\"", kOnStateCreate.data(), _stateType.data(), lua_isstring(L_, -1) ? luaW_tostring(L_, kIdxTop).data() : luaW_typename(L_, kIdxTop).data());
116 } 126 }
117 STACK_CHECK(L_, 0); 127 STACK_CHECK(L_, 0);
118} 128}
@@ -127,14 +137,14 @@ Universe* Universe::Create(lua_State* const L_)
127 static constexpr StackIndex kIdxSettings{ 1 }; 137 static constexpr StackIndex kIdxSettings{ 1 };
128 LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); 138 LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1));
129 STACK_CHECK_START_REL(L_, 0); // L_: settings 139 STACK_CHECK_START_REL(L_, 0); // L_: settings
130 std::ignore = luaG_getfield(L_, kIdxSettings, "nb_user_keepers"); // L_: settings nb_user_keepers 140 std::ignore = luaW_getfield(L_, kIdxSettings, "nb_user_keepers"); // L_: settings nb_user_keepers
131 int const _nbUserKeepers{ static_cast<int>(lua_tointeger(L_, -1)) + 1}; 141 int const _nbUserKeepers{ static_cast<int>(lua_tointeger(L_, -1)) + 1};
132 lua_pop(L_, 1); // L_: settings 142 lua_pop(L_, 1); // L_: settings
133 if (_nbUserKeepers < 1) { 143 if (_nbUserKeepers < 1) {
134 raise_luaL_error(L_, "Bad number of additional keepers (%d)", _nbUserKeepers); 144 raise_luaL_error(L_, "Bad number of additional keepers (%d)", _nbUserKeepers);
135 } 145 }
136 STACK_CHECK(L_, 0); 146 STACK_CHECK(L_, 0);
137 std::ignore = luaG_getfield(L_, kIdxSettings, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold 147 std::ignore = luaW_getfield(L_, kIdxSettings, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold
138 int const _keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) }; 148 int const _keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) };
139 lua_pop(L_, 1); // L_: settings 149 lua_pop(L_, 1); // L_: settings
140 STACK_CHECK(L_, 0); 150 STACK_CHECK(L_, 0);
@@ -147,22 +157,30 @@ Universe* Universe::Create(lua_State* const L_)
147 157
148 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); 158 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
149 lua_createtable(L_, 0, 1); // L_: settings universe {mt} 159 lua_createtable(L_, 0, 1); // L_: settings universe {mt}
150 std::ignore = luaG_getfield(L_, kIdxSettings, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout 160 std::ignore = luaW_getfield(L_, kIdxSettings, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout
151 lua_pushcclosure(L_, UniverseGC, 1); // L_: settings universe {mt} UniverseGC 161 lua_pushcclosure(L_, UniverseGC, 1); // L_: settings universe {mt} UniverseGC
152 lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt} 162 lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt}
153 lua_setmetatable(L_, -2); // L_: settings universe 163 lua_setmetatable(L_, -2); // L_: settings universe
154 lua_pop(L_, 1); // L_: settings 164 lua_pop(L_, 1); // L_: settings
155 165
156 std::ignore = luaG_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions 166 std::ignore = luaW_getfield(L_, kIdxSettings, "linda_wake_period"); // L_: settings linda_wake_period
167 if (luaW_type(L_, kIdxTop) == LuaType::NUMBER) {
168 _U->lindaWakePeriod = lua_Duration{ lua_tonumber(L_, kIdxTop) };
169 } else {
170 LUA_ASSERT(L_, luaW_tostring(L_, kIdxTop) == "never");
171 }
172 lua_pop(L_, 1); // L_: settings
173
174 std::ignore = luaW_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions
157 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false; 175 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false;
158 lua_pop(L_, 1); // L_: settings 176 lua_pop(L_, 1); // L_: settings
159 177
160 std::ignore = luaG_getfield(L_, kIdxSettings, "verbose_errors"); // L_: settings verbose_errors 178 std::ignore = luaW_getfield(L_, kIdxSettings, "verbose_errors"); // L_: settings verbose_errors
161 _U->verboseErrors = lua_toboolean(L_, -1) ? true : false; 179 _U->verboseErrors = lua_toboolean(L_, -1) ? true : false;
162 lua_pop(L_, 1); // L_: settings 180 lua_pop(L_, 1); // L_: settings
163 181
164 // tracking 182 // tracking
165 std::ignore = luaG_getfield(L_, kIdxSettings, "track_lanes"); // L_: settings track_lanes 183 std::ignore = luaW_getfield(L_, kIdxSettings, "track_lanes"); // L_: settings track_lanes
166 if (lua_toboolean(L_, -1)) { 184 if (lua_toboolean(L_, -1)) {
167 _U->tracker.activate(); 185 _U->tracker.activate();
168 } 186 }
@@ -170,8 +188,8 @@ Universe* Universe::Create(lua_State* const L_)
170 188
171 // Linked chains handling 189 // Linked chains handling
172 _U->selfdestructFirst = SELFDESTRUCT_END; 190 _U->selfdestructFirst = SELFDESTRUCT_END;
173 _U->initializeAllocatorFunction(L_); 191 _U->initializeAllocatorFunction(L_); // this can raise an error
174 _U->initializeOnStateCreate(L_); 192 _U->initializeOnStateCreate(L_); // this can raise an error
175 _U->keepers.initialize(*_U, L_, static_cast<size_t>(_nbUserKeepers), _keepers_gc_threshold); 193 _U->keepers.initialize(*_U, L_, static_cast<size_t>(_nbUserKeepers), _keepers_gc_threshold);
176 STACK_CHECK(L_, 0); 194 STACK_CHECK(L_, 0);
177 195
@@ -199,7 +217,7 @@ static void* libc_lua_Alloc([[maybe_unused]] void* const ud_, [[maybe_unused]] v
199// ################################################################################################# 217// #################################################################################################
200 218
201[[nodiscard]] 219[[nodiscard]]
202static int luaG_provide_protected_allocator(lua_State* const L_) 220static int luaW_provide_protected_allocator(lua_State* const L_)
203{ 221{
204 Universe* const _U{ Universe::Get(L_) }; 222 Universe* const _U{ Universe::Get(L_) };
205 // push a new full userdata on the stack, giving access to the universe's protected allocator 223 // push a new full userdata on the stack, giving access to the universe's protected allocator
@@ -209,6 +227,17 @@ static int luaG_provide_protected_allocator(lua_State* const L_)
209 227
210// ################################################################################################# 228// #################################################################################################
211 229
230// already called under protection of selfdestructMutex
231void Universe::flagDanglingLanes() const
232{
233 Lane* _lane{ selfdestructFirst };
234 while (_lane != SELFDESTRUCT_END) {
235 _lane->flaggedAfterUniverseGC.store(true, std::memory_order_relaxed);
236 _lane = _lane->selfdestruct_next;
237 }
238}
239// #################################################################################################
240
212// called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) 241// called once at the creation of the universe (therefore L_ is the master Lua state everything originates from)
213// Do I need to disable this when compiling for LuaJIT to prevent issues? 242// Do I need to disable this when compiling for LuaJIT to prevent issues?
214void Universe::initializeAllocatorFunction(lua_State* const L_) 243void Universe::initializeAllocatorFunction(lua_State* const L_)
@@ -216,24 +245,24 @@ void Universe::initializeAllocatorFunction(lua_State* const L_)
216 // start by just grabbing whatever allocator was provided to the master state 245 // start by just grabbing whatever allocator was provided to the master state
217 protectedAllocator.initFrom(L_); 246 protectedAllocator.initFrom(L_);
218 STACK_CHECK_START_REL(L_, 1); // L_: settings 247 STACK_CHECK_START_REL(L_, 1); // L_: settings
219 switch (luaG_getfield(L_, kIdxTop, "allocator")) { // L_: settings allocator|nil|"protected" 248 switch (luaW_getfield(L_, kIdxTop, "allocator")) { // L_: settings allocator|nil|"protected"
220 case LuaType::NIL: 249 case LuaType::NIL:
221 // nothing else to do 250 // nothing else to do
222 break; 251 break;
223 252
224 case LuaType::STRING: 253 case LuaType::STRING:
225 LUA_ASSERT(L_, luaG_tostring(L_, kIdxTop) == "protected"); 254 LUA_ASSERT(L_, luaW_tostring(L_, kIdxTop) == "protected");
226 // set the original allocator to call from inside protection by the mutex 255 // set the original allocator to call from inside protection by the mutex
227 protectedAllocator.installIn(L_); 256 protectedAllocator.installIn(L_);
228 // before a state is created, this function will be called to obtain the allocator 257 // before a state is created, this function will be called to obtain the allocator
229 provideAllocator = luaG_provide_protected_allocator; 258 provideAllocator = luaW_provide_protected_allocator;
230 break; 259 break;
231 260
232 case LuaType::FUNCTION: 261 case LuaType::FUNCTION:
233 provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator 262 provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator
234 if (provideAllocator != nullptr) { 263 if (provideAllocator != nullptr) {
235 // make sure the function doesn't have upvalues 264 // make sure the function doesn't have upvalues
236 char const* _upname = lua_getupvalue(L_, -1, 1); // L_: settings allocator upval? 265 char const* _upname{ lua_getupvalue(L_, -1, 1) }; // L_: settings allocator upval?
237 if (_upname != nullptr) { // should be "" for C functions with upvalues if any 266 if (_upname != nullptr) { // should be "" for C functions with upvalues if any
238 raise_luaL_error(L_, "config.allocator() shouldn't have upvalues"); 267 raise_luaL_error(L_, "config.allocator() shouldn't have upvalues");
239 } 268 }
@@ -247,19 +276,19 @@ void Universe::initializeAllocatorFunction(lua_State* const L_)
247 break; 276 break;
248 277
249 default: // should be filtered out in lanes.lua 278 default: // should be filtered out in lanes.lua
250 raise_luaL_error(L_, "Bad config.allocator type %s", luaG_typename(L_, kIdxTop).data()); 279 raise_luaL_error(L_, "Bad config.allocator type %s", luaW_typename(L_, kIdxTop).data());
251 } 280 }
252 lua_pop(L_, 1); // L_: settings 281 lua_pop(L_, 1); // L_: settings
253 STACK_CHECK(L_, 1); 282 STACK_CHECK(L_, 1);
254 283
255 std::ignore = luaG_getfield(L_, kIdxTop, "internal_allocator"); // L_: settings "libc"|"allocator" 284 std::ignore = luaW_getfield(L_, kIdxTop, "internal_allocator"); // L_: settings "libc"|"allocator"
256 LUA_ASSERT(L_, lua_isstring(L_, kIdxTop)); // should be the case due to lanes.lua parameter validation 285 LUA_ASSERT(L_, lua_isstring(L_, kIdxTop)); // should be the case due to lanes.lua parameter validation
257 std::string_view const _allocator{ luaG_tostring(L_, kIdxTop) }; 286 std::string_view const _allocator{ luaW_tostring(L_, kIdxTop) };
287 // use whatever the provider provides. This performs validation of what provideAllocator is giving
288 // we do this even if _allocator == "libc", to have the validation part
289 internalAllocator = resolveAndValidateAllocator(L_, "internal");
258 if (_allocator == "libc") { 290 if (_allocator == "libc") {
259 internalAllocator = lanes::AllocatorDefinition{ libc_lua_Alloc, nullptr }; 291 internalAllocator = lanes::AllocatorDefinition{ libc_lua_Alloc, nullptr };
260 } else {
261 // use whatever the provider provides
262 internalAllocator = resolveAllocator(L_, "internal");
263 } 292 }
264 lua_pop(L_, 1); // L_: settings 293 lua_pop(L_, 1); // L_: settings
265 STACK_CHECK(L_, 1); 294 STACK_CHECK(L_, 1);
@@ -276,7 +305,7 @@ int Universe::InitializeFinalizer(lua_State* const L_)
276 305
277 // make sure we are only called from the Master Lua State! 306 // make sure we are only called from the Master Lua State!
278 kUniverseFullRegKey.pushValue(L_); // L_: f U 307 kUniverseFullRegKey.pushValue(L_); // L_: f U
279 if (luaG_type(L_, kIdxTop) != LuaType::USERDATA) { 308 if (luaW_type(L_, kIdxTop) != LuaType::USERDATA) {
280 raise_luaL_error(L_, "lanes.%s called from inside a lane", kFinally); 309 raise_luaL_error(L_, "lanes.%s called from inside a lane", kFinally);
281 } 310 }
282 lua_pop(L_, 1); // L_: f 311 lua_pop(L_, 1); // L_: f
@@ -292,8 +321,8 @@ int Universe::InitializeFinalizer(lua_State* const L_)
292void Universe::initializeOnStateCreate(lua_State* const L_) 321void Universe::initializeOnStateCreate(lua_State* const L_)
293{ 322{
294 STACK_CHECK_START_REL(L_, 0); // L_: settings 323 STACK_CHECK_START_REL(L_, 0); // L_: settings
295 if (luaG_getfield(L_, kIdxTop, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil 324 if (luaW_getfield(L_, kIdxTop, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil
296 LUA_ASSERT(L_, luaG_type(L_, kIdxTop) == LuaType::FUNCTION); // ensured by lanes.lua parameter validation 325 LUA_ASSERT(L_, luaW_type(L_, kIdxTop) == LuaType::FUNCTION); // ensured by lanes.lua parameter validation
297 // store C function pointer in an internal variable 326 // store C function pointer in an internal variable
298 lua_CFunction const _func{ lua_tocfunction(L_, -1) }; // L_: settings on_state_create 327 lua_CFunction const _func{ lua_tocfunction(L_, -1) }; // L_: settings on_state_create
299 if (_func) { 328 if (_func) {
@@ -306,7 +335,7 @@ void Universe::initializeOnStateCreate(lua_State* const L_)
306 // remove this C function from the config table so that it doesn't cause problems 335 // remove this C function from the config table so that it doesn't cause problems
307 // when we transfer the config table in newly created Lua states 336 // when we transfer the config table in newly created Lua states
308 lua_pushnil(L_); // L_: settings on_state_create nil 337 lua_pushnil(L_); // L_: settings on_state_create nil
309 luaG_setfield(L_, StackIndex{ -3 }, kOnStateCreate); // L_: settings on_state_create 338 luaW_setfield(L_, StackIndex{ -3 }, kOnStateCreate); // L_: settings on_state_create
310 } else { 339 } else {
311 // the function is still in the config table. we indicate this with the uintptr_t alternative (actual value is irrelevant) 340 // the function is still in the config table. we indicate this with the uintptr_t alternative (actual value is irrelevant)
312 onStateCreateFunc.emplace<uintptr_t>(std::bit_cast<uintptr_t>(kOnStateCreate.data())); 341 onStateCreateFunc.emplace<uintptr_t>(std::bit_cast<uintptr_t>(kOnStateCreate.data()));
@@ -320,7 +349,7 @@ void Universe::initializeOnStateCreate(lua_State* const L_)
320 349
321// ################################################################################################# 350// #################################################################################################
322 351
323lanes::AllocatorDefinition Universe::resolveAllocator(lua_State* const L_, std::string_view const& hint_) const 352lanes::AllocatorDefinition Universe::resolveAndValidateAllocator(lua_State* const L_, std::string_view const& hint_) const
324{ 353{
325 lanes::AllocatorDefinition _ret{ protectedAllocator }; 354 lanes::AllocatorDefinition _ret{ protectedAllocator };
326 if (provideAllocator == nullptr) { 355 if (provideAllocator == nullptr) {
@@ -329,7 +358,7 @@ lanes::AllocatorDefinition Universe::resolveAllocator(lua_State* const L_, std::
329 358
330 STACK_CHECK_START_REL(L_, 0); // here, we have a function we can call to obtain an allocator 359 STACK_CHECK_START_REL(L_, 0); // here, we have a function we can call to obtain an allocator
331 lua_pushcclosure(L_, provideAllocator, 0); // L_: provideAllocator() 360 lua_pushcclosure(L_, provideAllocator, 0); // L_: provideAllocator()
332 luaG_pushstring(L_, hint_); // L_: provideAllocator() "<hint>" 361 luaW_pushstring(L_, hint_); // L_: provideAllocator() "<hint>"
333 lua_call(L_, 1, 1); // L_: result 362 lua_call(L_, 1, 1); // L_: result
334 // make sure we have a valid AllocatorDefinition on the stack (an error is raised instead if it is not the case) 363 // make sure we have a valid AllocatorDefinition on the stack (an error is raised instead if it is not the case)
335 _ret = lanes::AllocatorDefinition::Validated(L_, kIdxTop); 364 _ret = lanes::AllocatorDefinition::Validated(L_, kIdxTop);
@@ -399,11 +428,12 @@ bool Universe::terminateFreeRunningLanes(lua_Duration const shutdownTimeout_, Ca
399// ################################################################################################# 428// #################################################################################################
400 429
401// process end: cancel any still free-running threads 430// process end: cancel any still free-running threads
431// as far as I can tell, this can only by called only from lua_close()
402int Universe::UniverseGC(lua_State* const L_) 432int Universe::UniverseGC(lua_State* const L_)
403{ 433{
404 lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; 434 lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) };
405 STACK_CHECK_START_ABS(L_, 1); 435 STACK_CHECK_START_ABS(L_, 1);
406 Universe* const _U{ luaG_tofulluserdata<Universe>(L_, StackIndex{ 1 }) }; // L_: U 436 Universe* const _U{ luaW_tofulluserdata<Universe>(L_, StackIndex{ 1 }) }; // L_: U
407 437
408 // attempt to terminate all lanes with increasingly stronger cancel methods 438 // attempt to terminate all lanes with increasingly stronger cancel methods
409 bool const _allLanesTerminated{ 439 bool const _allLanesTerminated{
@@ -416,27 +446,52 @@ int Universe::UniverseGC(lua_State* const L_)
416 kFinalizerRegKey.pushValue(L_); // L_: U finalizer|nil 446 kFinalizerRegKey.pushValue(L_); // L_: U finalizer|nil
417 if (!lua_isnil(L_, -1)) { 447 if (!lua_isnil(L_, -1)) {
418 lua_pushboolean(L_, _allLanesTerminated); // L_: U finalizer bool 448 lua_pushboolean(L_, _allLanesTerminated); // L_: U finalizer bool
419 // no protection. Lua rules for errors in finalizers apply normally 449 // no protection. Lua rules for errors in finalizers apply normally:
420 lua_call(L_, 1, 1); // L_: U ret|error 450 // Lua 5.4+: error is propagated in the warn system
451 // older: error is swallowed
452 lua_call(L_, 1, 1); // L_: U msg?
453 // phew, no error in finalizer, since we reached that point
421 } 454 }
422 STACK_CHECK(L_, 2);
423 455
424 // 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 456 if (lua_isnil(L_, kIdxTop)) {
425 bool const _throw{ luaG_tostring(L_, kIdxTop) == "throw" }; 457 lua_pop(L_, 1); // L_: U
426 lua_pop(L_, 1); // L_: U 458 // no finalizer, or it returned no value: push some default message on the stack, in case it is necessary
459 luaW_pushstring(L_, "uncooperative lanes detected at shutdown"); // L_: U "msg"
460 }
461 STACK_CHECK(L_, 2);
427 462
428 while (_U->selfdestructFirst != SELFDESTRUCT_END) { 463 {
429 if (_throw) { 464 std::lock_guard<std::mutex> _guard{ _U->selfdestructMutex };
430 throw std::logic_error{ "Some lanes are still running at shutdown" }; 465 // now, all remaining lanes are flagged. if they crash because we remove keepers and the Universe from under them, it is their fault
466 bool const _detectedUncooperativeLanes{ _U->selfdestructFirst != SELFDESTRUCT_END };
467 if (_detectedUncooperativeLanes) {
468 _U->flagDanglingLanes();
469 if (luaW_tostring(L_, kIdxTop) == "freeze") {
470 std::this_thread::sleep_until(std::chrono::time_point<std::chrono::steady_clock>::max());
471 } else {
472 // take the value returned by the finalizer (or our default message) and throw it as an error
473 // since we are inside Lua's GCTM, it will be propagated through the warning system (Lua 5.4) or swallowed silently
474 // IMPORTANT: lua_error() is used here instead of the wrapper raise_lua_error() to circumvent what looks like a MSVC compiler bug
475 // that manifests as a crash inside ntdll!longjmp() function, in optimized builds only
476 lua_error(L_);
477 }
431 } else { 478 } else {
432 std::this_thread::yield(); 479 // we didn't use the error message, let's keep a clean stack
480 lua_pop(L_, 1); // L_: U
433 } 481 }
434 } 482 }
483 STACK_CHECK(L_, 1);
484
485 // ---------------------------------------------------------
486 // we don't reach that point if some lanes are still running
487 // ---------------------------------------------------------
435 488
436 // no need to mutex-protect this as all lanes in the universe are gone at that point 489 // no need to mutex-protect this as all lanes in the universe are gone at that point
437 Linda::DeleteTimerLinda(L_, std::exchange(_U->timerLinda, nullptr), PK); 490 Linda::DeleteTimerLinda(L_, std::exchange(_U->timerLinda, nullptr), PK);
438 491
439 _U->keepers.close(); 492 if (!_U->keepers.close()) {
493 raise_luaL_error(L_, "INTERNAL ERROR: Keepers closed more than once");
494 }
440 495
441 // remove the protected allocator, if any 496 // remove the protected allocator, if any
442 _U->protectedAllocator.removeFrom(L_); 497 _U->protectedAllocator.removeFrom(L_);