Lua weak tables memory leak

Viewed 708

I don't use often weak tables. However now I need to manage certain attributes for my objects which should be stored somewhere else. Thats when weak tables come in handy. My issue is, that they don't work es expected. I need weak keys, so that the entire key/value pair is removed, when the key is no longer referenced and I need strong values, since what is stored are tables with meta information which is only used inside that table, which also have a reference to the key, but somehow those pairs are never collected.

Code example:


local key = { }
local value = {
        ref = key,
        somevalue = "Still exists"
}

local tab = setmetatable({}, { __mode = "k" })

tab[key] = value

function printtab()
        for k, v in pairs(tab) do
                print(v.somevalue)
        end
end

printtab()

key = nil
value = nil

print("Delete values")
collectgarbage()

printtab()

Expected output:

Still exists
Delete values

Got:

Still exists
Delete values
Still exists

Why is the key/value pair not deleted? The only reference to value is effectivly a weak reference inside tab, and the reference inside value is not relevant, since the value itself is not used anywhere.

3 Answers

Ephemeron tables are supported since Lua 5.2.
The Lua 5.2 manual says:

A table with weak keys and strong values is also called an ephemeron table. In an ephemeron table, a value is considered reachable only if its key is reachable. In particular, if the only reference to a key comes through its value, the pair is removed.

Lua 5.1 does not support ephemeron tables correctly.

You are making too many assumptions about the garbage collector. Your data will be collected eventually. In this particular example it should work if you call collectgarbage() twice, but if you have some loops in your weak table it might take even longer.

EDIT: this actually only matters when you're waiting for the __cg event


I went over your code in more detail and noticed you have another problem.

Your value is referencing the key as well, creating a loop that is probably just too much for the GC of your Lua version to handle. In PUC Lua 5.3 this works as expected, but in LuaJIT the loop seems to keep the value from being collected.

This actually makes a lot of sense if you think about it; from what I can tell, the whole thing works by first removing weak elements from a table when they're not referenced anywhere else and thus leave them to be collected normally the next time the GC runs.

However, when this step runs, the key is still in the table, so the (not weak) value is a valid reference in the GCs eyes, as it is accessible from the code. So the GC kind of deadlocks itself into not being able to remove the key-value pair.

Possible solutions would be:

  1. Don't save a reference to the key in the value
  2. Make the value a weak table as well so it doesn't count as a reference either
  3. Upgrade to another Lua version
  4. Wrap the reference in a weak-valued single-element array

you can change the code like this. Then you will get the expected output. tips: do not reference key variable when you want it to be week.

local key = { }
local value = {
        -- ref = key,
        somevalue = "Still exists"
}

local tab = setmetatable({}, { __mode = "k" })

tab[key] = value

function printtab()
        for k, v in pairs(tab) do
                print(v.somevalue)
        end
end

printtab()

key = nil
value = nil

print("Delete values")
collectgarbage()

printtab()
Related