New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
This program creates garbage faster than the garbage collector can collect it #157
Comments
|
I cannot reproduce this issue with Lua 5.1 nor 5.2. It seems to be specific to Lua 5.3. local registry = {}
local refnil, noref = -1, -2
-- Lua-based reimplementations of luaL_ref and luaL_unref. Yes, this makes no
-- sense to have in Lua, but helps with the following experiments.
local function luaL_ref(t, obj)
if not obj then
return refnil
end
local next_free = t[0]
local ref
if next_free then
ref = next_free
t[0] = t[next_free]
else
ref = #t + 1
end
t[ref] = obj
return ref
end
local function luaL_unref(t, ref)
if ref >= 0 then
-- Push the free reference into the freelist
t[0], t[ref] = ref, t[0]
end
end
-- Quick test
do
local test_reg = {}
assert(luaL_ref(test_reg, nil) == refnil)
assert(luaL_ref(test_reg, {}) == 1)
assert(luaL_ref(test_reg, {}) == 2)
assert(luaL_ref(test_reg, {}) == 3)
assert(test_reg[0] == nil)
luaL_unref(test_reg, 2)
assert(test_reg[0] == 2)
assert(test_reg[2] == nil)
luaL_unref(test_reg, 1)
assert(test_reg[0] == 1)
assert(test_reg[1] == 2)
assert(test_reg[2] == nil)
assert(luaL_ref(test_reg, {}) == 1)
assert(test_reg[0] == 2)
assert(test_reg[2] == nil)
luaL_unref(test_reg, 3)
assert(test_reg[0] == 3)
assert(test_reg[3] == 2)
assert(test_reg[2] == nil)
luaL_unref(test_reg, 1)
assert(test_reg[0] == 1)
assert(test_reg[1] == 3)
assert(test_reg[3] == 2)
assert(test_reg[2] == nil)
assert(luaL_ref(test_reg, nil) == refnil)
end
-- Some helper functions
local function make_big_object(size)
if not size then return {} end
size = size or 3
if size <= 0 then
return {}
end
return { make_big_object(size-1), make_big_object(size-1) }
end
local function guard_gc_callback(self)
self.callback(self.arg1, self.arg2)
end
local guard_mt = { __gc = guard_gc_callback }
local function make_guard(callback, arg1, arg2)
setmetatable({callback = callback, arg1 = arg1, arg2 = arg2}, guard_mt)
end
local free_later = { make_big_object(20) }
-- Now simulate what LGI is doing when running the "leaky program"
local iter = 0
while true do
print(iter, collectgarbage("count"), #registry, registry[0])
for i = 1, 1000 do
local ref = luaL_ref(registry, make_big_object())
make_guard(luaL_unref, registry, ref)
iter = iter + 1
end
free_later = nil
end |
|
I asked on lua-l about this and got an answer from Roberto. It does not really say so, but I read it more-or-less as "do not do that". @pavouk Would it be ok with you if I tried to come up with some magic to help Lua's garbage collector here? I am thinking of adding a weak table that tracks the guards which can be cleared and uses this for "finalizing" these objects faster (when a new guard is created, clear all pending guards). |
|
I don't completely grok Roberto's answer too. From what I understood it seems that any use of luaL_ref can wreak havoc on GC if performed sufficiently often. This looks to me more like a bug of GC + luaL_ref. but most probably I'm missing something. @psychon Of course, if you come with any improvement I'll gladly merge it. And given recent amount of your and mine activity on lgi, I'd be really glad if you could at least co-maintain this project. If you are interested, please let me know. As for replacing luaL_ref: it used to be used in much more areas of lgi, but at some point I switched to use lua_setuserdata() instead. However, it is not so simple for closures, where more objects need to be assigned to single callback object (thread_ref, target_ref and callable_ref). That's why I left luaL_ref here. Maybe it would be possible to attach simple table as userdata and store all these three objects into it instead of using luaL_ref? |
Well, this is also how I end up maintaining cairo-xcb and working on libxcb (and a bit like I end up having no clue about Qt, but having reviewer rights), so sure, feel free. I'm used to this. :-)
That's indeed a good idea that could also help the Lua GC to figure out that something is not really reachable.
Sure, but I read the current use of guards as mostly as "this can be freed as soon as the current function returns"... I'll try to understand which parts needs which other parts to stay around and reduce the use of |
|
Sure, I definitely do not plan to retire completely, reviews and smaller-scale contributions are still fine. I'm a bit confused about you talking about guards. Is it |
Yes. I do not remember the Full Story (tm) right now, so half of the following is made-up. Something like this: /* Add guard which releases closure block after the
call. */
*lgi_guard_create (L, lgi_closure_destroy) = args[argi].v_pointer;The above delayed-destroys the closure. The closure however has So yes, the guards do not use My new plan would be to replace usage of |
|
After looking into the code (and mixing up Since I feel like my last answer did not make the problem clear enough: The problem is that two GC cycles are needed before some(?) callback can be freed. Since Lua uses the amount of memory used as an estimate on when the next GC cycle is needed, this creates a feedback loop:
|
Related-to: lgi-devs#157 Signed-off-by: Uli Schlachter <psychon@znc.in>
|
Random possibly relevant post on lua-l: http://lua-users.org/lists/lua-l/2018-04/msg00106.html |
|
While investigating my own AwesomeWM memory issues, I dug into this issue a bit - I discovered that many of the references in the Lua registry pointed to the same function and thread values. I was wondering - would be be reasonable to have a weakly-keyed table store a shared reference to things like callback functions and threads? That way if you have, say, 1000 marshaled callables, they would all share a reference to a single registry entry rather than creating their own. I imagine that it would work something like this: Create reference for
|
I'm not exactly sure what is happening here, but the following program seems to have ever-increasing memory usage:
When setting more agressive GC settings, this no longer happens. For example:
I looked a bit into "things" and I managed to figure out that the
finish_readfunction is still reachable via, for example.debug.getregistry()[34709]. This shows that lots of things are still referenced via the registry, which means vialuaL_ref. This function is only used for callables in lgi:lgi_closure_allocated()andlgi_closure_create().Callables are "left dangling" after use (
lgi_guard_create) and the GC can collect them. Apparently the above program causes some behaviour so that garbage accumulates quicker than it can be collected. Perhaps somehow the callables references each other...? (Although I do not really see how this would happen)Original issue: awesomeWM/awesome#1490
The text was updated successfully, but these errors were encountered: