lua-users home
lua-l archive

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


On 18/11/2018 15.34, Philippe Verdy wrote:
It's not very well documented, but when a finalizer gets called on an
object, just before calling it, the GC first clears the associated metatable if the object being finalized is a table: in the finalizer for an object whose type is 'table' or 'userdata', if you use getmetatable(self), it's not documented clearly if either you'll get nil, or you'll get the same metatable whose "__gc" entry is now nill,
something that should be better, allowing you to store the "cnt"
variable inside the metatable itself along with the "__gc" variable,
instead of the object being finalized).

That's complete nonsense.  Any modification of the metatable would be
unsafe as these are commonly used on several objects (though not in this
example), so the collection / finalization of the first such object
would break the finalization of all other objects with the same shared
metatable.

See §2 of https://www.lua.org/manual/5.3/manual.html#2.5.1 which says:

For an object (table or userdata) to be finalized when collected, you
must mark it for finalization. You mark an object for finalization
when you set its metatable and the metatable has a field indexed by
the string `"__gc"`. Note that if you set a metatable without a
`__gc` field and later create that field in the metatable, the object
will not be marked for finalization.

And §3

When a marked object becomes garbage, it is not collected immediately
by the garbage collector. Instead, Lua puts it in a list. After the
collection, Lua goes through that list. For each object in the list,
it checks the object's __gc metamethod: If it is a function, Lua
calls it with the object as its single argument; if the metamethod is
not a function, Lua simply ignores it.

And further §5

Because the object being collected must still be used by the finalizer, that object (and other objects accessible only through
it) must be resurrected by Lua. Usually, this resurrection is
transient, and the object memory is freed in the next
garbage-collection cycle. However, if the finalizer stores the object
in some global place (e.g., a global variable), then the resurrection
is permanent. Moreover, if the finalizer marks a finalizing object
for finalization again, its finalizer will be called again in the
next cycle where the object is unreachable. In any case, the object
memory is freed only in a GC cycle where the object is unreachable
and not marked for finalization.

(I wouldn't call that "not very well documented"…)

Rehashed in other (simpler?) words:

If, when you setmetatable(), there's _anything_ non-nil at `__gc` in the
metatable, the thing gets flagged for finalization. (This is a property of the table/userdata, not the metatable.)

When the thing is later collected and it has the "to be finalized" bit
set, this bit is cleared and, if _at this point_ the value at `__gc` in
the metatable is a function, that function gets run.

(And no matter what it'll do, the object survives until the next
collection.  Now _usually_, the "to be finalized" bit isn't re-enabled
by the `__gc` method and so the thing will be collected normally by the
next cycle… but you can re-flag it (by again calling setmetatable()
using a metatable with a `__gc` field), and even keep it around
indefinitely in an "undead" state – it's "dead" / fully unreachable from
the rest of the Lua state (hooks don't run during `__gc`), but it can
still do arbitrary stuff with the state.)


A fun / silly use of that is to make the computer beep on every
collection cycle:

setmetatable( {}, { __gc = function(t)
  io.stderr:write("\7") ; setmetatable(t,getmetatable(t)) end }
)

(This is easy to pre-load via the `-e` / `-l` options, and might be
useful for debugging… in fact, the Lua tests do something similar, just
writing a '.' for every collection instead of making it beep.)


You might also (ab)use this to trigger bookkeeping tasks (once per GC
cycle), if you have no better way to do that.  (A fixed "every $n
invocations of a function" scheme might not work (it could fire _both_
too rarely and too often, at different times), and in certain restricted
situations (games etc.), this might be as good as it gets… but note that
this is slightly racy – _any_ allocation can trigger a GC cycle, so
protect your data structures / make sure you're not reading inconsistent
state when triggered in the middle of some change.)


And of course there's LOTS of other stuff that you can do…

-- nobody