This Post Was Updated: (5/18/2023)
Currently as a Roblox developer is hard to work with the functions coroutine.close
and task.cancel
because the engine still assumes that threads are never cancellable, and that they will always remain alive. Which is not true, noting that these two functions set the coroutine to a dead state.
Whatâs the issue?
Since the release of these two functions, I have been adjusting my code base to cancel loops that are doing a certain task. In other words, for simple tasks where coroutine.yield
or task.wait
is called, everything itâs fine. However; if you call any other function that yields like Event:Wait
, Path:ComputeAsync
, RemoteFunction:Invoke
(just to mention a few), everything goes down hill.
Currently you would get the error âcannot resume dead coroutineâ on the output with no clear explanation from where it originated, tasks would still run on the back, and memory leaks would happen.
Examples Of Error Message: âcannot resume dead coroutineâ
local Thread = task.spawn(function()
workspace.ChildAdded:Wait()
end)
task.cancel(Thread)
Instance.new("Part", workspace)
local Thread = task.spawn(function()
end)
--> Thread is already dead
task.spawn(Thread, true) --> If you pass any value after the thread, it will trigger the error, else it doesn't.
Examples Of Memory Leaks:
This example is not as complex, but you can imagine the magnitude of it in other kinds of task where connections and many other things get leaked.
local function WaitForObject(Name)
local Thread = coroutine.running()
local Connection; Connection = workspace.ChildAdded:Connect(function(Object)
if Object.Name == Name then
Connection:Disconnect()
task.spawn(Thread, Object)
end
end)
return coroutine.yield()
end
local Thread = task.spawn(function()
print(WaitForObject("Part"))
end)
task.cancel(Thread) --> Thread is now dead, yet the connection is still in place.
--> Cannot resume dead coroutine shows up again after the event triggers.
Proposed Solution:
This is getting out of hands at this point, it looks like coroutine.close
and task.cancel
was never meant to be added in Luau or Lua in general. It looks like one of the dumbest functions I could have ever used since the only thing it does its change the Status of a coroutine to a dead state. No more than that, which prevent calls that are trying to resume the dead coroutine.
There is no way to know when a coroutine has been closed unless you fill up your code with repeat until
loops so we can cancel other tasks or disconnect connections, but no, I donât want to be doing that. I would love something that triggers once the thread sets to a dead state whether it was canceled or by itself.
The âperfectâ solution so it matches Lua behaviors would probably be something like xpcall
but on the coroutine library:
coroutine.join(Thread, Callback).
Once the Thread has ended or set to a dead state by coroutine.close
or task.cancel
, the Callback gets called.
This would be useful for us the developers to avoid memory leaks; however, Roblox should still go and disable the error âcannot resume dead coroutineâ and stop tasks on the background. I donât like things like Path:ComputeAsync
or external calls continue working while I cancelled the thread.
Example of a memory leak being fixed with the solution provided:
local function WaitForObject(Name)
local Thread = coroutine.running()
local Connection; Connection = workspace.ChildAdded:Connect(function(Object)
if Object.Name == Name then
Connection:Disconnect()
task.spawn(Thread, Object)
end
end)
coroutine.join(Thread, function()
Connection:Disconnect()
end)
return coroutine.yield()
end
local Thread = task.spawn(function()
print(WaitForObject("Part"))
end)
task.cancel(Thread) --> Thread is now dead and no connection in place.
Thank you, focasds.