Skip to content

Commit

Permalink
LuaFAR: Timer objects are garbage-collected as any other full userdata
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
shmuz committed Jan 19, 2025
1 parent 62f48b4 commit da3657d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 86 deletions.
8 changes: 4 additions & 4 deletions enc/enc_lua/luafar_manual.tsi
Original file line number Diff line number Diff line change
Expand Up @@ -8962,7 +8962,7 @@ lv=4
dt=Text
nm=Close
ctime=3482328964
mtime=3482491377
mtime=3946480903
<article>
#_timer:Close ()
#_
Expand All @@ -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.
#_
</article>
</node>
Expand Down
2 changes: 1 addition & 1 deletion plugins/luamacro/_globalinfo.lua
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
7 changes: 7 additions & 0 deletions plugins/luamacro/changelog
Original file line number Diff line number Diff line change
@@ -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
Expand Down
81 changes: 44 additions & 37 deletions plugins/luamacro/luafar/exported.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
89 changes: 46 additions & 43 deletions plugins/luamacro/luafar/service.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions plugins/luamacro/luafar/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion plugins/luamacro/luafar/version.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#include <farversion.hpp>

#define PLUGIN_BUILD 860
#define PLUGIN_BUILD 861

0 comments on commit da3657d

Please sign in to comment.