From da3657d90fa1c49930f56ed0e8a73db19ce06cd5 Mon Sep 17 00:00:00 2001 From: Shmuel Zeigerman Date: Sun, 19 Jan 2025 20:45:19 +0200 Subject: [PATCH] LuaFAR: Timer objects are garbage-collected as any other full userdata Was: "a timer object is garbage-collected only when its lua_State closes". Reason: reloading macros didn't release timers and their associated code. In some cases with frequent macros reloading it could lead to slowing down Far. --- enc/enc_lua/luafar_manual.tsi | 8 +-- plugins/luamacro/_globalinfo.lua | 2 +- plugins/luamacro/changelog | 7 +++ plugins/luamacro/luafar/exported.c | 81 ++++++++++++++------------- plugins/luamacro/luafar/service.c | 89 +++++++++++++++--------------- plugins/luamacro/luafar/util.h | 1 + plugins/luamacro/luafar/version.h | 2 +- 7 files changed, 104 insertions(+), 86 deletions(-) diff --git a/enc/enc_lua/luafar_manual.tsi b/enc/enc_lua/luafar_manual.tsi index 77048823a3..5e056442c6 100644 --- a/enc/enc_lua/luafar_manual.tsi +++ b/enc/enc_lua/luafar_manual.tsi @@ -8962,7 +8962,7 @@ lv=4 dt=Text nm=Close ctime=3482328964 -mtime=3482491377 +mtime=3946480903
#_timer:Close () #_ @@ -8976,9 +8976,9 @@ mtime=3482491377 #_ Closes the timer. #_ #_**Note:** -#_ A timer object is garbage-collected only when its lua_State closes, -#_ thus it is the application's responsibility to call the *Close* -#_ method when the timer is not needed anymore. +#_ Timer objects are subject to garbage collection but it +#_ is recommended to call the *Close* method when the timer +#_ is not needed anymore. #_
diff --git a/plugins/luamacro/_globalinfo.lua b/plugins/luamacro/_globalinfo.lua index 3dc50ffcff..b47d2f88b7 100644 --- a/plugins/luamacro/_globalinfo.lua +++ b/plugins/luamacro/_globalinfo.lua @@ -1,6 +1,6 @@ function export.GetGlobalInfo() return { - Version = { 3, 0, 0, 860 }, + Version = { 3, 0, 0, 861 }, MinFarVersion = { 3, 0, 0, 6380 }, Guid = win.Uuid("4EBBEFC8-2084-4B7F-94C0-692CE136894D"), Title = "LuaMacro", diff --git a/plugins/luamacro/changelog b/plugins/luamacro/changelog index 6724167d5e..5f233b2667 100644 --- a/plugins/luamacro/changelog +++ b/plugins/luamacro/changelog @@ -1,3 +1,10 @@ +shmuel 2025-01-19 20:14:03+02:00 - build 861 + +1. LuaFAR: Timer objects are garbage-collected as any other full userdata. + Was: "a timer object is garbage-collected only when its lua_State closes". + Reason: reloading macros didn't release timers and their associated code. + In some cases with frequent macros reloading it could lead to slowing down Far. + shmuel 2024-11-16 12:39:17+02:00 - build 860 1. Fix winapi.lua for arm64 diff --git a/plugins/luamacro/luafar/exported.c b/plugins/luamacro/luafar/exported.c index 21e7472e8a..3702ef6a1f 100644 --- a/plugins/luamacro/luafar/exported.c +++ b/plugins/luamacro/luafar/exported.c @@ -1499,51 +1499,58 @@ intptr_t LF_ProcessSynchroEvent(lua_State* L, const struct ProcessSynchroEventIn TSynchroData sd = *(TSynchroData*)Info->Param; // copy free(Info->Param); - if (sd.type & (SYNCHRO_TIMER_CALL | SYNCHRO_TIMER_UNREF)) + switch (sd.type) { - if (!sd.timerData->needClose && (sd.type & SYNCHRO_TIMER_CALL)) - { - lua_rawgeti(L, LUA_REGISTRYINDEX, sd.timerData->tabRef); //+1: Table - - if (lua_istable(L, -1)) + case SYNCHRO_TIMER_CALL: + if (!sd.timerData->needClose) { - int size, index; - lua_getfield(L, -1, "n"); //+2: table size - size = (int)lua_tointeger(L, -1); - for (index=1; index<=size; index++) - lua_rawgeti(L, -1-index, index); + lua_rawgeti(L, LUA_REGISTRYINDEX, sd.timerData->tabRef); //+1: Table - if (pcall_msg(L, size-1, 1) == 0) //+3 + if (lua_istable(L, -1)) { - if (lua_isnumber(L,-1)) ret = lua_tointeger(L,-1); - - lua_pop(L,3); + int size, index; + int tabPos = lua_gettop(L); + lua_getfield(L, tabPos, "n"); //+2: table size + size = (int)lua_tointeger(L, -1); + lua_rawgeti(L, tabPos, 1); // function + lua_rawgeti(L, LUA_REGISTRYINDEX, sd.timerData->hndRef); // weak table + lua_rawgeti(L, -1, 1); // Lua timer handle + lua_remove(L, -2); // remove the weak table from the stack + + for (index=2; index<=size; index++) // parameters + lua_rawgeti(L, tabPos, index); + + if (pcall_msg(L, size, 1) == 0) //+3 + { + if (lua_isnumber(L,-1)) ret = lua_tointeger(L,-1); + + lua_pop(L,3); + } + else lua_pop(L,2); } - else lua_pop(L,2); + else lua_pop(L, 1); } - else lua_pop(L, 1); - } + break; - if (sd.type & SYNCHRO_TIMER_UNREF) - { - luaL_unref(L, LUA_REGISTRYINDEX, sd.timerData->tabRef); - } - } - else if (sd.type == SYNCHRO_COMMON) - { - Common_ProcessSynchroEvent(L, Info->Event, sd.data); - } - else if (sd.type == SYNCHRO_FUNCTION) - { - lua_rawgeti(L, LUA_REGISTRYINDEX, sd.ref); - luaL_unref(L, LUA_REGISTRYINDEX, sd.ref); - if (lua_istable(L,-1) && lua_checkstack(L, sd.narg)) { - for (int i=1; i <= sd.narg; i++) { - lua_rawgeti(L, -i, i); + case SYNCHRO_TIMER_UNREF: + free(sd.timerData); + break; + + case SYNCHRO_COMMON: + Common_ProcessSynchroEvent(L, Info->Event, sd.data); + break; + + case SYNCHRO_FUNCTION: + lua_rawgeti(L, LUA_REGISTRYINDEX, sd.ref); + luaL_unref(L, LUA_REGISTRYINDEX, sd.ref); + if (lua_istable(L,-1) && lua_checkstack(L, sd.narg)) { + for (int i=1; i <= sd.narg; i++) { + lua_rawgeti(L, -i, i); + } + pcall_msg(L, sd.narg - 1, 0); } - pcall_msg(L, sd.narg - 1, 0); - } - lua_pop(L, 1); + lua_pop(L, 1); + break; } } else if (Info->Event == SE_FOLDERCHANGED) diff --git a/plugins/luamacro/luafar/service.c b/plugins/luamacro/luafar/service.c index 90ff622610..ae1c7016c0 100644 --- a/plugins/luamacro/luafar/service.c +++ b/plugins/luamacro/luafar/service.c @@ -5772,55 +5772,64 @@ void CALLBACK TimerCallback(void *lpParameter, BOOLEAN TimerOrWaitFired) static int far_Timer(lua_State *L) { - TPluginData *pd; TTimerData *td; - HANDLE hQueue; - int interval, index, tabSize; + int index, tabSize; + TPluginData *pd = GetPluginData(L); + HANDLE hQueue = GetLuaStateTimerQueue(L); - interval = (int)luaL_checkinteger(L, 1); + // check the first 2 mandatory parameters + int interval = (int)luaL_checkinteger(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); - tabSize = lua_gettop(L); - - lua_createtable(L, tabSize, 1); // place the function at [1] - lua_pushinteger(L, tabSize); - lua_setfield(L, -2, "n"); - lua_pushvalue(L, 2); - lua_rawseti(L, -2, 1); - - td = (TTimerData*)lua_newuserdata(L, sizeof(TTimerData)); - luaL_getmetatable(L, FarTimerType); - lua_setmetatable(L, -2); - lua_pushvalue(L, -1); - lua_rawseti(L, -3, 2); // place the userdata at [2] - - for (index=3; index<=tabSize; index++) // place the arguments, if any - { - lua_pushvalue(L, index); - lua_rawseti(L, -3, index); - } - - pd = GetPluginData(L); + // malloc timer data to remain valid after garbage collection + td = (TTimerData*) malloc(sizeof(TTimerData)); td->Info = pd->Info; td->PluginGuid = pd->PluginId; td->interval = interval < 1 ? 1 : interval; - - lua_pushvalue(L, -2); - td->tabRef = luaL_ref(L, LUA_REGISTRYINDEX); td->needClose = FALSE; td->enabled = 1; - hQueue = GetLuaStateTimerQueue(L); + + // create a table holding the Lua function and its parameters + tabSize = lua_gettop(L) - 1; // exclude "interval" + lua_createtable(L, tabSize, 1); + lua_pushinteger(L, tabSize); // place the table size at ["n"] + lua_setfield(L, -2, "n"); // + + + // place the function and the arguments (if any) + for (index=1; index<=tabSize; index++) { + lua_pushvalue(L, index+1); + lua_rawseti(L, -2, index); + } + td->tabRef = luaL_ref(L, LUA_REGISTRYINDEX); // place tbl in the registry (and pop off the stack) + + // create a Lua-side timer handle + TTimerData** LuaHandle = (TTimerData**) lua_newuserdata(L, sizeof(TTimerData*)); + *LuaHandle = td; + luaL_getmetatable(L, FarTimerType); + lua_setmetatable(L, -2); + + // create a weak table, holding the timer handle, put the table into registry + lua_createtable(L, 1, 1); // wtable = {} + lua_pushvalue(L, -2); // wtable[1] = LuaHandle + lua_rawseti(L, -2, 1); // + + lua_pushliteral(L, "v"); // wtable.__mode = "v" + lua_setfield(L, -2, "__mode"); // + + lua_pushvalue(L, -1); // setmetatable(wtable,wtable) + lua_setmetatable(L, -2); // + + td->hndRef = luaL_ref(L, LUA_REGISTRYINDEX); // place wtable in the registry (and pop off the stack) if (hQueue && CreateTimerQueueTimer(&td->hTimer,hQueue,TimerCallback,td,td->interval,td->interval,WT_EXECUTEDEFAULT)) return 1; + luaL_unref(L, LUA_REGISTRYINDEX, td->hndRef); luaL_unref(L, LUA_REGISTRYINDEX, td->tabRef); + free(td); return lua_pushnil(L), 1; } TTimerData* CheckTimer(lua_State* L, int pos) { - return (TTimerData*)luaL_checkudata(L, pos, FarTimerType); + return *(TTimerData**)luaL_checkudata(L, pos, FarTimerType); } TTimerData* CheckValidTimer(lua_State* L, int pos) @@ -5832,32 +5841,26 @@ TTimerData* CheckValidTimer(lua_State* L, int pos) static int timer_Close(lua_State *L) { - HANDLE hQueue; - TSynchroData* sd; TTimerData* td = CheckTimer(L, 1); if (!td->needClose) { td->needClose = TRUE; - hQueue = GetLuaStateTimerQueue(L); + HANDLE hQueue = GetLuaStateTimerQueue(L); if (hQueue) DeleteTimerQueueTimer(hQueue, td->hTimer, NULL); - sd = CreateSynchroData(SYNCHRO_TIMER_UNREF, 0, td); - td->Info->AdvControl(td->PluginGuid, ACTL_SYNCHRO, 0, sd); } return 0; } static int timer_gc(lua_State *L) { - HANDLE hQueue; + timer_Close(L); TTimerData* td = CheckTimer(L, 1); - if (!td->needClose) - { - td->needClose = TRUE; - hQueue = GetLuaStateTimerQueue(L); - if (hQueue) - DeleteTimerQueueTimer(hQueue, td->hTimer, NULL); - } + luaL_unref(L, LUA_REGISTRYINDEX, td->tabRef); + luaL_unref(L, LUA_REGISTRYINDEX, td->hndRef); + td->tabRef = td->hndRef = LUA_NOREF; + TSynchroData* sd = CreateSynchroData(SYNCHRO_TIMER_UNREF, 0, td); + td->Info->AdvControl(td->PluginGuid, ACTL_SYNCHRO, 0, sd); return 0; } diff --git a/plugins/luamacro/luafar/util.h b/plugins/luamacro/luafar/util.h index d85f3e4e33..ada6e1b180 100644 --- a/plugins/luamacro/luafar/util.h +++ b/plugins/luamacro/luafar/util.h @@ -39,6 +39,7 @@ typedef struct struct PluginStartupInfo *Info; int interval; // timer period, in milliseconds int tabRef; // reference of a Lua table in the registry + int hndRef; // reference of {timer_handle} in the registry int needClose; // timer needs to be closed; boolean value int enabled; // timer is enabled; the callback function is called only when (enabled != 0) HANDLE hTimer; // timer handle diff --git a/plugins/luamacro/luafar/version.h b/plugins/luamacro/luafar/version.h index 1726e60ece..3b27775d4e 100644 --- a/plugins/luamacro/luafar/version.h +++ b/plugins/luamacro/luafar/version.h @@ -1,3 +1,3 @@ #include -#define PLUGIN_BUILD 860 +#define PLUGIN_BUILD 861