Odp: Re: __gc userdata problem

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Odp: Re: __gc userdata problem

Dominik Zaczkowski
On, 26 Aug 2013 20:38 Kevin Martin wrote:

> On 26 Aug 2013, at 15:02, Dominik Zaczkowski wrote:
>
> > I have problem with userdata and garbage collection, until now I thought that after userdata __gc
> > metamethod is called it means that lua cannot touch my data in any way,
>
>
> It doesn't work the way you want it to. imagine if it did, how is the garbage collector supposed to dispose of cyclic data structures. For example, in the below code, A and B are both unreachable, which gc method should be called first?
>
> local m = {}
> function m.__gc(self)
> print(string.format("Garbage Collecting %s, still paired with %s", self.name, self.paired.name)
> end
>
> do
> local A = {name = "A"}
> local B = {name = "B"}
>
> setmetatable(A, m)
> setmetatable(B, m)
>
> A.paired = B
> B.paired = A
> end
>
> --A and B both unreachable, but reference each other.
>
> > How can I be sure that my userdata is gone, and I can free its resources?
> > Only solution that I see is to mark userdata as free on __gc and then check for that it in every
> > function exposed to lua.
>
> Either that or change the userdata's metatable to one where __index and __newindex both raise errors - that way if any code tries to access it, an error is raised.
>
> Thanks,
> Kev

Thank you Kevin, now i understand why my data is accessed. But I still don't get how __index and __newindex metamethods could help, my functions exposed to lua are not allways userdata methods - and that was your point, to hide them, right?

I tried to throw lua_error() in my wrapper function which gets the userdata, but sadly i got panic on lua_gc(), changeing userdata metatable also doesn't work, as luaL_userdata idirectly calls lua_error(). I guess only option is to every time check if my ud has been collected.



Reply | Threaded
Open this post in threaded view
|

Re: __gc userdata problem

Kevin Martin

On 29 Aug 2013, at 15:57, Dominik Zaczkowski wrote:

> Thank you Kevin, now i understand why my data is accessed. But I still don't get how __index and __newindex metamethods could help, my functions exposed to lua are not allways userdata methods - and that was your point, to hide them, right?

Yes, my point was to make the methods non-accessible. I understand that not all your functions are userdata methods, however, surely it is only the userdata methods that will be affected by garbage collection. The calling of a __gc method should not affect any global methods or methods of other objects.

> I tried to throw lua_error() in my wrapper function which gets the userdata, but sadly i got panic on lua_gc(),

That is because an error is being thrown and lua_gc isn't protected. This can be handled by calling lua_gc inside a pcall, you will get a return value of LUA_ERRGCMM (At least you will in 5.2), along with a suitable message on the stack.

Attached is some code that creates two user datas that reference each other and call each other in their gc metamethod. As you can see from the output, the first succeeds, but the second fails with the message "Tried to access a dead userdata", this is done by changing the metatable of the userdata.

Thanks,
Kev

#include <lua5.2/lua.h>
#include <lua5.2/lauxlib.h>
#include <stdio.h>
#include <assert.h>

static int accessInvalidObj(lua_State *l) {
        return luaL_error(l, "Tried to access a dead user data");
}

static int setDeadObjMT(lua_State *l) {
        lua_settop(l, 1);
       
        lua_rawgetp(l, LUA_REGISTRYINDEX, setDeadObjMT);
        if(lua_type(l, -1) == LUA_TNIL) {
                lua_newtable(l);
                lua_replace(l, 2);

                lua_pushcfunction(l, accessInvalidObj);
                lua_setfield(l, -2, "__index");
               
                lua_pushcfunction(l, accessInvalidObj);
                lua_setfield(l, -2, "__newindex");

                lua_pushvalue(l, -1);
                lua_rawsetp(l, LUA_REGISTRYINDEX, setDeadObjMT);
        }

        lua_setmetatable(l, 1);
        return 0;
}

static int userdataGC(lua_State *l) {
        const char *code = "local uv = ... for k,v in pairs(uv) do print(k, v:alive()) end";
        luaL_loadstring(l, code);
        lua_getuservalue(l, 1);
        lua_call(l, 1, 0);
       
        lua_pushcfunction(l, setDeadObjMT);
        lua_pushvalue(l, 1);
        lua_call(l, 1, 0);

        printf("Userdata has been GC'ed\n");
        return 0;
}

static int userdataStore(lua_State *l) {
        lua_settop(l, 3);

        lua_getuservalue(l, 1);
        lua_replace(l, 1);

        lua_settable(l, -3);
        return 0;
}

static int userdataAlive(lua_State *l) {
        lua_pushboolean(l, 1);
        return 1;
}

static int setUserDataMT(lua_State *l) {
        lua_settop(l, 1);
       
        lua_rawgetp(l, LUA_REGISTRYINDEX, setUserDataMT);
        if(lua_type(l, -1) == LUA_TNIL) {
                lua_newtable(l);
                lua_replace(l, 2);

                lua_pushvalue(l, -1);
                lua_setfield(l, -2, "__index");

                lua_pushcfunction(l, userdataGC);
                lua_setfield(l, -2, "__gc");

                lua_pushcfunction(l, userdataStore);
                lua_setfield(l, -2, "store");

                lua_pushcfunction(l, userdataAlive);
                lua_setfield(l, -2, "alive");

                lua_pushvalue(l, -1);
                lua_rawsetp(l, LUA_REGISTRYINDEX, setUserDataMT);
        }

        lua_setmetatable(l, 1);
        return 0;
}

static int newUserData(lua_State *l) {
        lua_settop(l, 0);

        void *ud = lua_newuserdata(l, 1);

        lua_newtable(l);
        lua_setuservalue(l, -2);

        lua_pushcfunction(l, setUserDataMT);
        lua_pushvalue(l, 1);
        lua_call(l, 1, 0);

        return 1;
}

static int protectedgc(lua_State *l) {
        lua_gc(l, LUA_GCCOLLECT, 0);
        return 0;
}

int main(int argc, char **argv) {
        lua_State *l = luaL_newstate();
        luaL_openlibs(l);

        lua_pushcfunction(l, newUserData);
        lua_setglobal(l, "nud");

        const char *code =
         "do; local a, b = nud(), nud(); a:store(\"b\", b); b:store(\"a\", a); end";

        luaL_loadstring(l, code);
        if(lua_pcall(l, 0, 0, 0) != LUA_OK) {
                printf("pcall failed: %s\n", lua_tostring(l, -1));
        }

        lua_pushcfunction(l, protectedgc);
        if(lua_pcall(l, 0, 0, 0) != LUA_OK) {
                printf("pcall failed: %s\n", lua_tostring(l, -1));
        }

        lua_close(l);
        return 0;
}