Question about __gc ordering and cross-reference safety

54 views
Skip to first unread message

重归混沌

unread,
Oct 29, 2025, 10:42:36 PM (8 days ago) Oct 29
to lua-l

Hello everyone,

I’ve encountered a question about the order and safety of __gc metamethod calls in Lua, and I’d like to confirm whether my usage is correct and what the best practice might be.

The attached document contains a simplified example.

When I run this code using `lua a.lua`, I get the following output:


```
lnew:0x601f976a68b8
qcreate silly.adt.queue: 0x601f976a68b8
--gc:0x601f976a68b8
qpush x:        silly.adt.queue: 0x601f976a68b8
```

It seems that the queue userdata’s __gc is invoked before xxx.__gc, even though the latter still references x inside its finalizer.

I’m wondering if I might be misunderstanding how __gc works.
If this behavior is expected, what would be the proper or recommended way to avoid such a situation?

Thank you very much for your time and help!

lqueue.c
x.lua
y.lua

Sainan

unread,
Oct 30, 2025, 4:51:16 AM (8 days ago) Oct 30
to lu...@googlegroups.com
This is the atexit GC, which I think does not really respect what's still referenced because everything needs to be freed anyway. If you let the variables go out of scope and then called collectgarbage(), you would not see this behaviour.

The atexit GC deletes things in the opposite order of creation. In your case, you create the table {k=1} with your pushing __gc BEFORE you call y.foo() to create the queue instance, hence why it gets cleaned up in that way.

-- Sainan

Roberto Ierusalimschy

unread,
Oct 30, 2025, 9:27:43 AM (8 days ago) Oct 30
to 'Sainan' via lua-l
> The atexit GC deletes things in the opposite order of creation. In your case, you create the table {k=1} with your pushing __gc BEFORE you call y.foo() to create the queue instance, hence why it gets cleaned up in that way.

Just to complement: This does not apply only to "atexit GC". Every time
two objects are released together, their finalizers are called in the
reverse "order of creation". So, if A is the only reference to B, and
A is released, B will be released too. The order of finalization between
A and B does not depend on A refering B, but on A being "created" before
or after B.

To be precise, the order of finalization does not depend on the order of
creation (hence the quotes above), but on the order the objects get
their metatable with __gc. It is a good practice to add the metatable
to an ojbect just after its creation, so in practice this detail should
make little difference.

-- Roberto

Sainan

unread,
Oct 30, 2025, 10:30:02 AM (8 days ago) Oct 30
to lu...@googlegroups.com
> This does not apply only to "atexit GC".

Hmm, now I wonder why I could not reproduce it with the variables going out of scope and calling collectgarbage().

Should maybe look more into the GC internals, this feels like it could trip me up with a use-after-free on some userdata.

-- Sainan
Reply all
Reply to author
Forward
0 new messages