lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi all,

I've been trying today to find out a way to portably set an on-unload 
function for my C modules and after cogitating on loadlib.c for a 
while, determined that the best way to do it was to store in the 
registry a userdatum a __gc metamethod.  This store would happen at 
luaopen_<lib>() time.  This ought to work, as far as I can tell, 
because the userdatum stored into the registry by 
loadlib.c:ll_require() to unload my module should be finalized _after_ 
the userdatum I store there.  The relevant bit of the manual here is 
in 2.10.1 where it says:

	"At the end of each garbage-collection cycle, the finalizers 
	for userdata are called in reverse order of their creation, 
	among those collected in that cycle. That is, the first 
	finalizer to be called is the one associated with the 
	userdata created last in the program."

What worries me a little is that it doesn't actually say that the 
userdata in a table which is garbage are all collected in the same 
cycle, leading to the possibility that the LIFO ordering implied above 
is violated over multiple cycles.  Whipping up a small test case shows 
that something is indeed going wrong:

$ lua /tmp/gctest.lua 
fail    16900   finalized after 8957
fail    100000  finalized after 16900
$ 

The test case creates 100000 userdata with individual __gc metamethods 
using newproxy() and stores them into a table.  It then nils the 
variable holding the table and calls collectgarbage().  The __gc 
metamethods check that they are called in LIFO order.

So what that test shows is that the 16000th created userdatum in the 
test is finalized after the 8957th userdatum, and likewise for the 
100000'th userdatum, so the test seems to trigger a finalizer order 
violation.  Inspecting the order of finalization with more prints, it 
seems that the order of finalized userdata is: 8957, 16900, 100000, 
99999, 99998, ...

I'm not well versed in Lua's garbage collector code and would be 
obliged if someone could explain what's going on.  Any hints on how to 
make a non-hacky on-unload function would be great too.

Cheers,

Joonas

8<--------------8<-----------------8<---------------
local last_finalized = math.huge

local function mkgc(i)
    return function ()
               if i > last_finalized then
                   print("fail", i, "finalized after", last_finalized)
               end
               last_finalized = i
           end
end

local t = {}
for i=1,1e5 do
    local key = tostring(math.random())
    local u = newproxy(true)
    getmetatable(u).__gc = mkgc(i)
    t[key] = u
end
t = nil

collectgarbage()