lua-users home
lua-l archive

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


[Idea for a GC extension at the bottom]

didier@derny.org wrote:
> 
> I have some application like that
> 
> function  xxx ()
>   local handler1 =  function_that_return_userdata()
>   local handler2 = another_function_that_return_userdata()
>   ...
> end
> 
> I installed a metatable on the userdata for __gc to close to end
> the processing, close files... but handler2 needs to be destroyed before
> handler1
> 
> is there a way to predict how the garbage collector will collect the
> variable or do I  need to add some code to keep track of the opened
> handler2 ?

As was already mentioned, userdata are collected in reverse
order of creation but only when collected within the same
cycle.  Example:

  function foo()
    local ud1 = new_userdata()
    local ud2 = new_userdata()
  end
  foo()
  collectgarbage()

Here "collectgarbage" will first call __gc(ud2) and then __gc(ud1).

Now here:

  function foo()
    local ud1 = new_userdata()
    local ud2 = new_userdata()
    bar = ud2
  end
  foo()
  collectgarbage()
  bar=nil
  collectgarbage()

the order of __gc calls is reversed.  __gc(ud1) will be called
from the first collectgarbage and __gc(ud2) from the second.

To circumvent that (you _always_ want __gc(ud2) be called before
__gc(ud1)) you have to make sure that ud2 will not be collected
before ud1.  You can do that by creating a reference from ud2
to ud1.  That means, as long as ud2 is alive, ud1 will also be
alive.

Unfortunately, it's not easy for a userdata object to reference
another object directly.  You have to create these dependencies
by some other means, i.e. by an additional table:

  dependency = {}
  dependency[ud2] = ud1

But now you have the problem, that ud1/2 will never be collected.
They are both referenced by the dependecy table.  And here comes
the use of weak tables: you declare that the keys of this table
are "weak" which means, they won't be considered references by
the garbage collector and further, dead keys are removed from the
table during collection.

  dependency = {}
  setmetatable(dependency, { __mode = "k" })
  dependency[ud2] = ud1

Now, as long as ud2 is alive, ud1 is always also alive through
the dependency table - it can't be collected.
   But at the moment ud2 becomes dead (the reference from the
key of the dependency table doesn't count - it's weak) the
garbage collector will remove the entry from the table, the
reference to ud1 vanishes and ud1 may be collected too.

---------

Now for an idea to extend the garbage collector:

    If the __gc function returns a non-nil value the userdata
    is kept within Lua as if there is still a reference to it.

That way the __gc function could check whether there are still
(internal) references to the userdata (via a reference count
or a child-list, or ...) and can tell the GC that the object
has to stay valid.  Much easier and way faster than anything
else.

I haven't looked at how difficult it would be to implement that.
There may be some subtle details ...  (mixes mark and collect)

Ciao, ET.