问题
I am attempting to create a GC finalizer for a function value by storing it in a weak table using the C API.
I started off by 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'
Resulting output:
Called myfunc
Called finalizer
Closing Lua
The prototype seems to be working as intended. Below is the C version:
#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"); // f t
lua_pushvalue(L, 1); // f t k
lua_newuserdata(L, 0); // f t k v
lua_newtable(L); // f t k v mt
lua_pushcfunction(L, my_finalizer); // f t k v mt v
lua_setfield(L, -2, "__gc"); // f t k v mt
lua_setmetatable(L, -2); // f t k v
lua_settable(L, -3); // f t
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
Resulting output:
Called myfunc
Closing Lua
Called finalizer
The C version has a few minor differences:
- Instead of a local
sentinels
table I am storing in the registry. - Using a zero sized userdata instead of a table for sentinel value with
__gc
metamethod.
I am confused as to why in the C version the myfunc
finalizer doesn't execute after running a full collection cycle. What am I doing wrong?
回答1:
As the Lua manual states:
Only objects that have an explicit construction are removed from weak tables. Values, such as numbers and light C functions, are not subject to garbage collection, and therefore are not removed from weak tables (unless its associated value is collected).
Your my_func
is pushed without any upvalues, so it is a light C function, and it isn't removed from weak tables during garbage collection, so the associated userdata does not become garbage before you close the Lua state. Your code should work if you use a Lua function instead of my_func
, or if you push my_func
with upvalues (and if you fix the order of the arguments in the lua_gc
call!).
To summarize, the following value types are not removed from weak tables (given that their associated keys/values aren't removed either):
- booleans
- numbers
- strings
- light userdata
- light C functions (Lua 5.2 only)
As a consequence your program should work fine with Lua 5.1 because there are no light C functions (you still have to fix the lua_gc
call).
来源:https://stackoverflow.com/questions/24087890/weak-table-and-gc-finalizer-using-c-api