Answer the Question First
Lua 5.4 does not provide any priority or dependency mechanism for the __gc metamethod. The garbage collector chooses an arbitrary order for finalizing unreachable userdata, and that order can differ between runs or Lua builds. Therefore, you cannot rely on the GC to clean up interdependent resources in a predictable sequence.
Recommended Pattern for Safe Cleanup
The most reliable approach is to centralise control of the cleanup process in an explicit manager object. The manager holds references (weak or strong, depending on the desired lifetime) to all interdependent userdata and implements a deterministic __gc that calls the individual cleanup functions in the order you specify.
Typical Implementation Steps
- Define cleanup functions for each resource. These functions should perform the actual release of system handles or other side‑effects and must not assume the existence of other resources.
- Create a manager userdata. Store the resource references in a table inside the manager. Use
debug.setmetatable or normal metatable assignment to give the manager its own __gc.
- Implement the manager’s
__gc. In this function, call the resource cleanup functions in the desired order. Because the manager is the only object that triggers the calls, the order is deterministic.
- Drop all external references. When the application no longer needs the resources, simply remove references to the manager. The manager’s finalizer will run on the next GC cycle and perform the cleanup.
- Optionally trigger GC manually. If you need the cleanup to happen immediately, call
collectgarbage("collect") after dropping references.
Example Code
-- Resource cleanup functions
local function close_socket(sock) sock:close() end
local function free_buffer(buf) buf:free() end
-- Manager userdata
local manager = newproxy(true) -- or use a table with a userdata field
local mt = getmetatable(manager)
mt.__gc = function(self)
-- deterministic order: socket first, then buffer
close_socket(self.sock)
free_buffer(self.buf)
print("Resources cleaned up")
end
-- Store references
manager.sock = socket_object
manager.buf = buffer_object
-- When done, drop references
manager = nil
collectgarbage("collect")
Why This Works
- The manager’s
__gc is the only finalizer that can be ordered, because it is a single point of control.
- Individual resources’
__gc metamethods can be left empty or used for safety nets.
- Weak references inside the manager prevent accidental strong retention of resources beyond the desired scope.
What You Cannot Do
- There is no Lua API to set a priority for a userdata’s
__gc.
- Relying on the GC order can lead to double releases or use‑after‑free bugs if one finalizer accesses another already finalized object.
- The GC may delay finalization until the next cycle; if immediate cleanup is required, call
collectgarbage("collect") or perform explicit cleanup.
Ask for Missing Diagnostic Detail (if needed)
If you need to guarantee immediate release of external handles (e.g., sockets that must close before the next network operation), consider adding an explicit close method on the manager and invoking it before dropping the reference.