So for some reason threads link back to the env / tbl they were called in referencing it and preventing a true garbage collection to take place.
For Example:
wow = setmetatable({}, {__index = _G})
wow.__index = wow
wow.__gc = function() print("del WOW") end
function wow:new(typeOf, id, index, job, thisTask)
local this = {}
print(" <-- new wow envoked")
this.group = index
this.active = true
--this._G = _G
this.__gc = function()
print(" removed from wow")
end
setmetatable(this, self)
self.__index = self
return this
end
function wow:doTest()
Citizen.CreateThread(function()
-- true can be replaced by self or true and it still will work
-- self is not deleted till the thread is terminated
while self.active do
print("Thread still here")
Citizen.Wait(1000)
end
print("TASK DEAD")
Citizen.SetTimeout(400, function() collectgarbage("collect") end)
return
end)
end
RegisterCommand("doHighT", function()
local sX = wow:new(1, 2, 3, 4, 5)
local tbl = {}
tbl[1] = {task = sX}
sX:doTest()
print(sX, sX.group)
sX = nil
print(sX, tbl[1]['task'])
tbl[1]['task'] = nil
collectgarbage("collect")
-- this wont work as there is still a refrance in the thread preventing it to delete so neither of the __gc are envoked
-- however
sX = wow:new(1, 2, 3, 4, 5)
tbl = {}
tbl[1] = {task = sX}
sX:doTest()
print(sX, sX.group)
sX.active = false
sX = nil
print(sX, tbl[1]['task'])
tbl[1]['task'] = nil
collectgarbage("collect")
end)
Now I looked into the code for creating a thread in the scheduler however I cant see where it is referencing / storing where it is being called from.
**THIS CODE IS AN EXAMPLE, I DONT CARE WHAT THE CODE DOES **
this is just to show that the thread prevents the __gc from happening.
The first part (before the comments in the command) show an example of how it does not work and get hang time and does not delete. The second part, after the “however” shows how the thread needs to be terminated before the garbage collection takes place.
-- defines a function with one argument, `self`
function wow:doTest()
-- defines a function with one upvalue, said `self`
-- upvalues are only closed implicitly when the function ends
Citizen.CreateThread(function()
-- function will only end when self.active is false
while self.active do
Doesn’t set self.active to false so the function won’t end, and it will still have a live reference to the self on which it’s waiting for .active since the function is implicitly closing on it.
From the Lua 5.3 manual:
Because of the lexical scoping rules, local variables can be freely accessed by functions defined inside their scope. A local variable used by an inner function is called an upvalue, or external local variable, inside the inner function.
Lua manages memory automatically by running a garbage collector to collect all dead objects (that is, objects that are no longer accessible from Lua).
self is accessible from your function in the thread.
The function in the thread is Lua.
Therefore, self is accessible from Lua.
As such, self is not ‘dead’ and will not be collected by the GC.
In your ‘however’ example, self.active is set to false, so the function reaches the return, and after this both the function and its upvalue are not accessible from Lua and will be collected by the GC.
If the ‘however’ example also does not collect, however, this is probably an actual bug.
Thanks for explaining it, yes the however case does work as intended. I’ll look into a different way to terminate threads with “local” upvalues that are preserved. I am going to look more into ‘weak tables’ to see if I can destroy the ‘self’ reference within the thread from outside it.
Weak tables are the mechanism that you use to tell Lua that a reference should not prevent the reclamation of an object. A weak reference is a reference to an object that is not considered by the garbage collector. If all references pointing to an object are weak, the object is collected and somehow these weak references are deleted. Lua implements weak references as weak tables: A weak table is a table where all references are weak. That means that, if an object is only held inside weak tables, Lua will collect the object eventually.
Now the self is a weak reference, this should mean that when the other references are destroyed this reference should be garbage collected too. (have not tested this yet but will update this when I get the chance to test it )
local b = {}
setmetatable(self, b)
b.__mode = "k"
Citizen.CreateThread(function()
-- true can be replaced by self or true and it still will work
-- self is not deleted till the thread is terminated
while self and self.active do