Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weak table and GC finalizer using C API

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:

  1. Instead of a local sentinels table I am storing in the registry.
  2. 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?

like image 954
Adam Avatar asked Jun 06 '14 17:06

Adam


1 Answers

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).

like image 77
siffiejoe Avatar answered Nov 05 '22 15:11

siffiejoe