You'd think that Lua source code is unavailable if you read this thread. The finalizer implementation is fairly simple to understand:
- if you call 'setmetatable' on an object, and the metatable has a __gc field the object is moved from the 'allgc' list to the 'finobj' list. The FINALIZEDBIT is set in the object's GC state.
- when the object becomes unreachable it is moved back to the 'allgc' list, and the FINALIZEDBIT is reset. The finalizer is then called.
- when the object becomes unreachable again (i.e. when it's on the 'allgc' list and there are no references to it) it's freed.
If you call 'setmetatable' in the finalizer, and that metatable has a '__gc' field the object is moved back to the 'finobj' list, so the process starts again and the object is never freed.
Observations:
- an object is only finalized once, unless you explicitly call 'setmetatable' again to set the finalizer.
- it's not a bug, the original poster's program explicitly requests that the object be finalized again using 'setmetatable'. The behavior is consistent with the documentation.
- there are other things you can do in the __gc function, like storing a reference to the object somewhere so it stays reachable. The finalizer has run, but the object will have to be kept around anyway because it just became reachable again. Having the finalizer 'decide' what to do is therefor unsafe. Freeing an object whose finalizer has not run yet requires two GC cycles, at the end of the first one the finalizer runs, at the end of the second one the object is freed.
- It's not clear to me what the use case is for changing the metatable in a finalizer to begin with. In the normal case you'd expect the object to be unreachable after the finalizer returns, so it will be freed in the next pass.
"It's not a bug, it's a feature!"