From c64f9dcd61c1ad7bef3dbf5b7647a2a2da23ac0f Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Fri, 5 Apr 2024 08:49:48 +0200 Subject: Enable manual control of GC inside keeper states --- src/keeper.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++-------- src/keeper.h | 1 + src/lanes.lua | 5 +++++ src/linda.cpp | 17 ++++++++++++----- 4 files changed, 64 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/keeper.cpp b/src/keeper.cpp index 244cb6a..937d190 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -652,12 +652,18 @@ void init_keepers(Universe* U, lua_State* L) { STACK_CHECK_START_REL(L, 0); // L K lua_getfield(L, 1, "nb_keepers"); // nb_keepers - int nb_keepers{ static_cast(lua_tointeger(L, -1)) }; + int const nb_keepers{ static_cast(lua_tointeger(L, -1)) }; lua_pop(L, 1); // if (nb_keepers < 1) { std::ignore = luaL_error(L, "Bad number of keepers (%d)", nb_keepers); } + STACK_CHECK(L, 0); + + lua_getfield(L, 1, "keepers_gc_threshold"); // keepers_gc_threshold + int const keepers_gc_threshold{ static_cast(lua_tointeger(L, -1)) }; + lua_pop(L, 1); // + STACK_CHECK(L, 0); // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states { @@ -668,6 +674,7 @@ void init_keepers(Universe* U, lua_State* L) std::ignore = luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); } memset(U->keepers, 0, bytes); + U->keepers->gc_threshold = keepers_gc_threshold; U->keepers->nb_keepers = nb_keepers; } for (int i = 0; i < nb_keepers; ++i) // keepersUD @@ -685,6 +692,11 @@ void init_keepers(Universe* U, lua_State* L) // therefore, we need a recursive mutex. MUTEX_RECURSIVE_INIT(&U->keepers->keeper_array[i].keeper_cs); + if (U->keepers->gc_threshold >= 0) + { + lua_gc(K, LUA_GCSTOP, 0); + } + STACK_CHECK_START_ABS(K, 0); // copy the universe pointer in the keeper itself @@ -735,8 +747,12 @@ void init_keepers(Universe* U, lua_State* L) Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_) { int const nbKeepers{ keepers_->nb_keepers }; - unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); - return &keepers_->keeper_array[i]; + if (nbKeepers) + { + unsigned int i = (unsigned int) ((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); + return &keepers_->keeper_array[i]; + } + return nullptr; } // ################################################################################################## @@ -745,11 +761,7 @@ Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_) { int const nbKeepers{ keepers_->nb_keepers }; // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) - if( nbKeepers == 0) - { - return nullptr; - } - else + if (nbKeepers) { /* * Any hashing will do that maps pointers to 0..GNbKeepers-1 @@ -765,6 +777,7 @@ Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_) //++ K->count; return K; } + return nullptr; } // ################################################################################################## @@ -843,5 +856,30 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi } // whatever happens, restore the stack to where it was at the origin lua_settop(K, Ktos); + + // don't do this for this particular function, as it is only called during Linda destruction, and we don't want to raise an error, ever + if (func_ != KEEPER_API(clear)) [[unlikely]] + { + // since keeper state GC is stopped, let's run a step once in a while if required + int const gc_threshold{ U->keepers->gc_threshold }; + if (gc_threshold == 0) [[unlikely]] + { + lua_gc(K, LUA_GCSTEP, 0); + } + else if (gc_threshold > 0) [[likely]] + { + int const gc_usage{ lua_gc(K, LUA_GCCOUNT, 0) }; + if (gc_usage >= gc_threshold) + { + lua_gc(K, LUA_GCCOLLECT, 0); + int const gc_usage_after{ lua_gc(K, LUA_GCCOUNT, 0) }; + if (gc_usage_after > gc_threshold) [[unlikely]] + { + luaL_error(L, "Keeper GC threshold is too low, need at least %d", gc_usage_after); + } + } + } + } + return retvals; } diff --git a/src/keeper.h b/src/keeper.h index e081bea..f7e3951 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -24,6 +24,7 @@ struct Keeper struct Keepers { + int gc_threshold{ 0 }; int nb_keepers; Keeper keeper_array[1]; }; diff --git a/src/lanes.lua b/src/lanes.lua index b4c0070..6af286a 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -70,6 +70,7 @@ lanes.configure = function( settings_) local default_params = { nb_keepers = 1, + keepers_gc_threshold = -1, on_state_create = nil, shutdown_timeout = 0.25, with_timers = true, @@ -91,6 +92,10 @@ lanes.configure = function( settings_) -- nb_keepers should be a number > 0 return type( val_) == "number" and val_ > 0 end, + keepers_gc_threshold = function( val_) + -- keepers_gc_threshold should be a number + return type( val_) == "number" + end, with_timers = boolean_param_checker, allocator = function( val_) -- can be nil, "protected", or a function diff --git a/src/linda.cpp b/src/linda.cpp index 37a74b0..5ee4768 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -885,15 +885,22 @@ static void* linda_id( lua_State* L, DeepOp op_) { Linda* const linda{ lua_tolightuserdata(L, 1) }; ASSERT_L(linda); - - // Clean associated structures in the keeper state. - Keeper* const K{ keeper_acquire(linda->U->keepers, linda->hashSeed()) }; - if (K && K->L) // can be nullptr if this happens during main state shutdown (lanes is GC'ed -> no keepers -> no need to cleanup) + Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; + // if collected after the universe, keepers are already destroyed, and there is nothing to clear + if (myK) { + // if collected from my own keeper, we can't acquire/release it + // because we are already inside a protected area, and trying to do so would deadlock! + bool const need_acquire_release{ myK->L != L }; + // Clean associated structures in the keeper state. + Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0); + if (need_acquire_release) + { + keeper_release(K); + } } - keeper_release(K); delete linda; // operator delete overload ensures things go as expected return nullptr; -- cgit v1.2.3-55-g6feb