This question might be for Lua and tolua experts.
I'm using tolua++1.0.93, and lua-5.1.4 (CEGUI 0.84 dependencies). I have been tracking this nasty memory leak for couple of hours, and I've found that toluapp creates tolua_gc table in Lua registry and it seems that this table grows infinitely.
When I push my object to Lua using tolua_pushusertype_and_takeownership I want Lua's GC to delete my object. And it does so, but tolua_pushusertype_and_takeownership calls tolua_register_gc which puts this objects metatable under object as a key to this "global" tolua_gc table. When tolua_gc_event function calls collector function (which calls delete operator) it that sets nil value to tolua_gc table under just deleted object as a key. So that should work, right?
Well, no.
Maybe I understood something wrong, but it seems that this has no effect on size of the tolua_gc table. I have also tried to manually call tolua.releaseownership(object) from Lua. And it worked. I mean, it decreased memory used by Lua (LUA_GCCOUNT) but since it disconnected collector from object, operator delete is never called and it created memory leaks in C++.
It is really strange behavior, because all tolua.releaseownership does is it sets nil value to tolua_gc table under passed object as a key. So why tolua.releaseownership decreases size of memory used by Lua, and tolua_gc_event does not? The only difference is that tolua.releaseownership calls garbage collector before it sets nil to tolua_gc table, and tolua_gc_event is called by garbage collector (opposite situation).
Why do we need that global tolua_gc table? Can't we just take metatable from object directly at the moment of collection?
I have really limited memory that I can use from this process (8MB) and it seems that this tolua_gc table occupies like 90% of it after some time.
How can I fix this?
Thank you.
EDIT: These are code samples:
extern unsigned int TestSSCount;
class TestSS
{
public:
double d_double;
TestSS()
{
// TestSSCount++;
// fprintf(stderr, "c(%d)\n",TestSSCount);
}
TestSS(const TestSS& other)
{
d_double = other.d_double * 0.5;
// TestSSCount++;
// fprintf(stderr, "cc(%d)\n",TestSSCount);
}
~TestSS()
{
// TestSSCount--;
// fprintf(stderr, "d(%d)\n", TestSSCount);
}
};
class App
{
...
TestSS doNothing()
{
TestSS t;
t.d_double = 13.89;
return t;
}
void callGC()
{
int kbs_before = lua_gc(d_state, LUA_GCCOUNT, 0);
lua_gc(d_state, LUA_GCCOLLECT, 0);
int kbs_after = lua_gc(d_state, LUA_GCCOUNT, 0);
printf("GC changed memory usage from %d kB to %d kB, difference %d kB",
kbs_before, kbs_after, kbs_before - kbs_after);
}
...
};
This is .pkg file:
class TestSS
{
public:
double d_double;
};
class App
{
TestSS doNothing();
void callGC();
};
Now complete Lua code (app and rootWindow are C++ objects provided as constants to Lua):
function handleCharacterKey(e_)
local key = CEGUI.toKeyEventArgs(e_).scancode
if key == CEGUI.Key.One then
for i = 1,10000,1 do
-- this makes GC clear all memory from Lua heap but does not call destructor in C++
-- tolua.releaseownership(app:doNothing())
-- this makes GC call destructors in C++ but somehow makes Lua heap increase constantly
app:doNothing()
elseif key == CEGUI.Key.Zero then
app:callGC()
end
end
rootWindow:subscribeEvent("KeyUp", "handleCharacterKey")
This is output that I get when pressing 0 1 0 1 0 1 0:
This is when I use tolua.releaseowenership
GC changed memory usage from 294 kB to 228 kB, difference 66 k
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
This is without tolua.releaseownership:
GC changed memory usage from 294 kB to 228 kB, difference 66 kB
GC changed memory usage from 605 kB to 604 kB, difference 1 kB
GC changed memory usage from 982 kB to 861 kB, difference 121 kB
GC changed memory usage from 1142 kB to 1141 kB, difference 1 kB
And this is without releaseownership but sequnce that I press on the keyboard is 0 1 0 1 0 1 0 0 0 0 (three more extra calls to GC at the end)
GC changed memory usage from 294 kB to 228 kB, difference 66 kB
GC changed memory usage from 603 kB to 602 kB, difference 1 kB
GC changed memory usage from 982 kB to 871 kB, difference 111 kB
GC changed memory usage from 1142 kB to 1141 kB, difference 1 kB
GC changed memory usage from 1141 kB to 868 kB, difference 273 kB <- this is after first additional GC call
GC changed memory usage from 868 kB to 868 kB, difference 0 kB
GC changed memory usage from 868 kB to 868 kB, difference 0 kB
Problem is not bug or memory leak. Although you could say it's memory leak if you have really limited memory. The problem is that tolua_gc, being lua table as it is, does not rehash when you remove elements by setting them to nil.
Although I supposed it could be the problem, I was stupid enough not to see if it is true. So garbage collector can not force table to rehash and shrink its size. So table will grow until certain insert that will trigger rehash. Read this: http://www.lua.org/gems/sample.pdf
So at the end I've removed tolua_gc table and I put metatables (that tolua had used to put in tolua_gc table with lightuserdata as a key) as special field in userdata object itself. And also instead of accessing those metatables from tolua_gc table, I get it now from object itself. Everything else is the same, and it seems to work.