Weak table and GC finalizer using C API

I am trying to create a GC finalizer for a function value, storing it in a weak table using the C API.

I started writing a prototype in pure Lua 5.2:

local function myfinalizer() print 'Called finalizer' end function myfunc() print 'Called myfunc' end local sentinels = setmetatable({}, { __mode='k' }) sentinels[myfunc] = setmetatable({}, { __gc=myfinalizer }) myfunc() myfunc = nil collectgarbage 'collect' print 'Closing Lua' 

Result:

 Called myfunc Called finalizer Closing Lua 


The prototype seems to work as intended. The following is version C:

 #include <stdlib.h> #include <stdio.h> #include "lua.h" #include "lualib.h" #include "lauxlib.h" static int my_finalizer(lua_State *L) { puts("Called finalizer"); return 0; } static int my_func(lua_State *L) { puts("Called myfunc"); return 0; } int main(void) { lua_State *L = luaL_newstate(); luaL_openlibs(L); // create sentinels table (weak keys) in registry lua_newtable(L); // t lua_newtable(L); // t mt lua_pushstring(L, "k"); // t mt v lua_setfield(L, -2, "__mode"); // t mt lua_setmetatable(L, -2); // t lua_setfield(L, LUA_REGISTRYINDEX, "sentinels"); // // push global function and register as sentinel lua_pushcfunction(L, my_func); // f lua_getfield(L, LUA_REGISTRYINDEX, "sentinels"); // ft lua_pushvalue(L, 1); // ftk lua_newuserdata(L, 0); // ftkv lua_newtable(L); // ftkv mt lua_pushcfunction(L, my_finalizer); // ftkv mt v lua_setfield(L, -2, "__gc"); // ftkv mt lua_setmetatable(L, -2); // ftkv lua_settable(L, -3); // ft lua_pop(L, 1); // f lua_setglobal(L, "myfunc"); // // execute test script and exit if (luaL_dostring(L, "myfunc(); myfunc=nil; collectgarbage'collect'")) { printf("Error: %s\n", lua_tostring(L, -1)); } lua_gc(L, LUA_GCCOLLECT, 0); // suggestion: two full gc cycles fflush(stdout); // suggestion: immediate flush puts("Closing Lua"); lua_close(L); fflush(stdout); return EXIT_SUCCESS; } 

Compiled using:

 $ gcc -std=c99 -Wall -Werror -pedantic -O2 -o main main.c -ldl -llua52 -lm 

Result:

 Called myfunc Closing Lua Called finalizer 

Version C has a few minor differences:

  • Instead of the local sentinels table, I store it in the registry.
  • Using user data of zero size instead of a table for a sentinel value with the __gc __gc .

I am confused why in version C the myfunc finalizer does not execute after starting the full collection loop. What am I doing wrong?

+6
source share
1 answer

As stated in the Lua manual:

Only objects with an explicit construction are deleted from weak tables. Values, such as numbers and light C functions, are not garbage collected and therefore are not removed from weak tables (unless their associated value is collected).

Your my_func without any enhancements, so it is a lightweight C function, and it is not removed from weak tables during garbage collection, so the associated user data does not become garbage before you close the Lua state. Your code should work if you use the Lua function instead of my_func , or if you press my_func with upvalues ​​(and if you correct the order of the arguments in the lua_gc call!).

To summarize, the following types of values ​​are not deleted from weak tables (given that their associated keys / values ​​are not deleted):

  • boolean
  • the numbers
  • strings
  • light userdata li>
  • C light functions (only for Lua 5.2)

As a result, your program should work fine with Lua 5.1 because there are no easy C functions (you still have to fix the lua_gc call).

+5
source

Source: https://habr.com/ru/post/970477/


All Articles