I agree, the example shown marks the same object for finalization in the next cycle by setting again its metatable with the MT table that has a declared __gc key mapped to the function; when it is called (the object is being finalized), the finalizer here unconditionnally marks the object for being finalized in a later cycle. So the finalizer will be called indefinitely, once at each cycle.
function MT:__gc()
self.cnt = self.cnt + 1
if self.cnt == 1 then
print("finalizing...")
setmetatable(self, MT) -- mark for later finalization
else
print("and again...")
end
end
This way only the first finalization (self.cnt==1) will displaying "finalizing..." and mark the object to be finalized again later (by restoring its metatable); the next cycle one will cause the finalizer to be called again but then self.cnt will be 2 and you'll get the message "and again...", but now the object is not marked to be finalized again, so it will be effectively collected (you should no longer see the "and again..." message more than once after the single "finalizing..." message).
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).