Task methods can thread leak

Reproduction Steps

The following snippet of code reproduces the bug:

local thread = coroutine.create(function(...)
    task.wait()
    return nil -- nil can be anything, as long as there is at least one result
end)
coroutine.resume(thread)
task.wait(1)
print(coroutine.status(thread)) -- suspended

This snippet also reproduces the issue (function was run by the task scheduler, initiated by task.spawn, and returned more than one result):

local thread
task.spawn(function()
    thread = coroutine.running()
    return 123
end)
task.wait(1)
print(coroutine.status(thread)) -- suspended

The thread will remain suspended, despite having very clearly exited. The results can be printed out from coroutine.resume, and thus the thread has very blatantly exited and returned and should be dead. However this is not the case at all, the function remains in a suspended state. Seemingly the thread remains in memory without ever garbage collecting, though I am not sure if my testing was very accurate.

The following snippet does not reproduce this issue (no return results, no bug):

local thread = coroutine.create(function(...)
    task.wait()
    return -- This return can be omitted, I figured that keeping things as close to the repro snippet would be beneficial
end)
coroutine.resume(thread)
task.wait(1)
print(coroutine.status(thread)) -- dead (no return results)

In summary:

  • Any thread resumed or started by the task scheduler will not become dead after returning, given that the body function of the thread has more at least one return result.
  • If the thread has no return results it will become dead.
  • This effects all task functions including resumptions from task.wait.
  • It is relevant to note that ModuleScript bodies must return a result so assuming they rely on the same scheduling mechanism, this bug likely affects all loaded ModuleScripts.

P.s. I have marked the status of this bug as “high” because I rely on thread statuses to determine when some user-entered code exits (and this bug consistently prevents me from detecting that), as well as this seemingly resulting in a memory leak. I apologize if this is an incorrect way to mark this issue, I wasn’t entirely sure.

Expected Behavior

Any thread resumed by the task scheduler which exits should have a dead status regardless of what it returns.

Actual Behavior

Any thread resumed by the task scheduler which has one or more return results will remain in a suspended status indefinitely, and never GC.

Issue Area: Engine
Issue Type: Other
Impact: High
Frequency: Constantly

2 Likes

Thanks for the report. I have logged this internally for us to take a look at.

2 Likes

Hey, I just wanted to mention that this bug is still occurring.

1 Like

Thread state ‘suspended’ doesn’t prevent GC from collecting the thread, so there is no memory leak here.

Thank you again for the report.

We do not plan to fix the status of the coroutine which interacts with the task library.
In general, we do not recommend mixing coroutine and task library use on the same thread.

The results which are returned from the thread to the task library are planned to be potentially used in new task APIs and as such, that thread has to remain ‘suspended’.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.