lua-users home
lua-l archive

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


Am 24.11.2015 um 15:04 schröbte Roberto Ierusalimschy:
First, if the library does not protect metatables, any code can get the
__gc metamethod and call it explicitly.

IMHO, protecting the metatable is a good idea anyway so that no-one
removes the `__gc` metamethod and you run out of resources somewhere
else.

Someone can trivially hold a reference to the object so that you
run out of resources. There is no need to remove __gc.

Even if I provide a custom environment, or `lua_close()` the Lua state?



Second, it is a good practice (well, I think it is) to provide an
explicit way to "close" an object that needs finalization.

The io library, for instance, does that.

It's easy for the io library, because it stores pointers. Downside
is that the garbage collector isn't aware of the extra memory, and
memory fragmentation could be higher since there are multiple
allocations for each userdata now. For every non-pointer userdata
you'd have to add an extra field to indicate the state of
finalization.

1 bit is not that expensive.

The size is not the issue (although with padding the one bit can cost you up to 8 bytes). The point is that you cannot use

    lua_newuserdata( L, sizeof( Fl_Input ) );

You have to write

    typedef struct {
      Fl_Input obj;
      char     valid;
    } Fl_Input_UD;
    //...
    lua_newuserdata( L, sizeof( Fl_Input_UD ) );

(Unless you add some flags to the Lua `Userdata` type and an API to access those, see below).



But now every (meta-)method has to check that the userdata is still
valid, and most Lua code that uses those (meta-)methods has to check
again unless it is ok with a simple getter/setter throwing an error.

Most of these methods have to check that the userdata has the correct
type, anyway. It should be easy to bundle together these two tests
(the object has the correct type *and* it is not closed.)

I have written a wrapper around `luaL_newmetatable()`[1], `lua_newuserdata()`[2][3], and `luaL_checkudata()`[4] to do just that[5]. It also handles the struct vs pointer difference transparently (i.e. the check function always returns `Fl_Input*` regardless of whether an `Fl_Input` or an `Fl_Input*` is stored in the userdata) and can check the validity of the parent object if you want to expose members of structs, unions or classes as userdata.

  [1]: https://github.com/siffiejoe/lua-moon#moon_defobject
  [2]: https://github.com/siffiejoe/lua-moon#moon_newobject
  [3]: https://github.com/siffiejoe/lua-moon#moon_newpointer
  [4]: https://github.com/siffiejoe/lua-moon#moon_checkobject
  [5]: https://github.com/siffiejoe/lua-moon#moon_killobject



Also there are cases where explicitly "closing" an object is unsafe,
e.g. if another object holds a pointer to that object. You can
ensure the correct `__gc` order by storing references in the
appropriate uservalue tables, but invalidating dependent objects is
harder -- especially if the dependencies might change at runtime.
Concrete examples from the last three libraries I created bindings
for are renderers and textures in libSDL, memory pools and any other
APR object in the Apache Portable Runtime library, and
Fl_Input_Choice and its Fl_Input and Fl_Menu_Button subwidgets in
FLTK.

It is worth remembering that, by using simple tricks with weak tables,
it is not difficult to allow an object to be naturally finalized (that
is, it has its __gc called by the collector) *and* to keep a reference
to it. So, if you are worried about that situation, you need some
kind of extra protection anyway; it is not enough to hide the __gc
from the user.

Ah, I didn't realize that. It seems I have to remove the metatable from some of my finalized objects to avoid that issue for now.

(A better technique in some cases is to hide the
object itself from the user.)


I have (another) unrelated question:
After a `lua_pcall(L, 0, LUA_MULTRET, 0)`, does Lua have `EXTRA_STACK` stack slots available for its API functions? I.e. if I don't want to call `lua_checkstack()`, do I have to free only the stack slots I need myself or the `EXTRA_STACK` stack slots for the Lua API as well?


-- Roberto


Philipp