Skip to content

references to python objects can be randomly lost in lua 5.2 and 5.3 #293

Open

Description

@thisismypassport

[NOTE - edited as I'm no longer sure I understand this fully]

Usually immediately, but sometimes after a few retries or some time, the below example raises an error because the dict created from the lua table was not able to find the key in the table (since it became unequal to itself) and so put nil instead

import lupa.lua52 as lual 

lrt = lual.LuaRuntime()

while True:
    lrt.globals().K = lambda: object()
    lrt.globals().dict = lambda o: dict(o)

    lrt.execute("""
    k = K()
    t = {[k]=1}
    a = dict(t)
    """)

    if (tuple(lrt.globals().a.values()) != (1,)):
        print(lrt.globals().a)
        raise 0

Reproduces in lua 5.2 and 5.3, but not 5.4 or up.

I have investigated this quite a bit, but I'm not quite sure I understand this...

Still, it seems related to the PYREFST table - which is used with luaL_ref/unref, but is also a weak table (__mode = 'v').

This means:

  • Being a weak table, lua is free to nil-out arbitrary entries in it while objects get collected.
  • luaL_ref can get the length of the table, which in lua is defined to return an arbitrary index whose next entry is nil, not necessarily the last one.

I originally said this means luaL_ref can return a wrong index, but I'm no longer sure that's correct - I don't see anything wrong with luaL_ref returning an index in the middle of the table, as long as it is currently nil (which it should be, one way or the other)

However, it might be useful to know that some things that cause the issue to no longer reproduce are:

  • Making the table strong (naturally a full leak)
  • Making luaL_ref not rely on table length (instead tracking it itself manually)
  • Using lua 5.4 and up, which are less likely to return a non-last value for the table length (but only less likely - still possible and still legal)

Note that there is also a leak here, thinking about it - there is no guarantee that luaL_ref will ever find the niled-out-due-to-gc entries, especially in lua 5.4 and up.

Activity

  1. changed the title [-]references are randomly lost (at least in lua 5.2 and 5.3) due to using luaL_ref/unref with a weak table[/-] [+]references to python objects can be randomly lost in lua 5.2 and 5.3[/+] on Jul 19, 2026
  2. thisismypassport commented on Jul 19, 2026

    @thisismypassport
    Author

    (Another piece of the puzzle might be that lua appears to [per atomic in lgc.c] to empty weak tables before calling finalizers)

    [EDIT - I've worked around this on my fork's side, so I'm not personally blocked by it anymore]

  3. sprakes commented on Oct 15, 2026

    @sprakes

    I think I fixed it. The slot numbers for PYREFST come from luaL_ref on that same weak table.

    Lua clears dead wrappers out of the weak table before it runs the wrapper’s gc. luaL_ref only keeps numbers unique among entries still in the table. So every once in a while the slot gets reused by a new wrapper while the old gc is still pending.

    The old __gc then does luaL_unref on the new wrapper’s slot and pops its _pyrefs_in_lua entry. Your dict(t) re-pushes k, hits the emptied slot, and gets a fresh wrapper.

    I made a fix with tests in #299

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions