Task.defer and task.delay have strange behavior when trying to resume a thread that is not suspended

Reproduction Steps

The functions task.defer and task.delay have strange behavior when the coroutine provided to them is not suspended, or if the coroutine is being scheduled for resumption while already being scheduled to be resumed later.

Expected Behavior

If the coroutine provided to task.defer or task.delay is dead, nothing should happen.
If the coroutine provided to task.defer or task.delay is running or normal, it should be resumed correctly when it would normally be resumed.
If a coroutine is being scheduled for resumption while already being scheduled to be resumed later, these should not interfere and both resumptions should happen correctly.

Actual Behavior

If the coroutine provided to task.defer or task.delay is dead, the provided arguments will still be put into the thread (which can resurrect the thread!):

local co = task.spawn(function()end)
print(coroutine.status(co)) -- prints "dead"
task.defer(co,print) -- puts print onto the coroutine
-- which makes it look like a freshly created coroutine
print(coroutine.status(co)) -- prints "suspended"
task.spawn(co,1,2,3) -- prints "1 2 3"
print(coroutine.status(co)) -- prints "dead"

If the coroutine provided to task.defer or task.delay is running or normal, the coroutine will be resumed but without any of the extra arguments provided:

task.defer(coroutine.running(),true)
print(coroutine.yield()) -- prints nothing

If a coroutine is being scheduled for resumption while already being scheduled, some strange stuff happens:

local co = coroutine.create(function(...)
	warn(...)
	while true do
		warn(coroutine.yield())
	end
end)
task.defer(co,print,1,2,3)
task.defer(co,print)
-- printed normally:
-- 1 2 3 function: 0xXXXXXXXXXXXXXXXX
-- error printed:
-- cannot resume dead coroutine

Workaround

These issues can be worked around by creating a new coroutine that will solely resume the original coroutine.
For example:

task.delay(1,task.spawn,coroutine.running(),true) -- will create a new coroutine that just resumes the main coroutine
print(coroutine.yield())
task.defer(task.spawn,task.spawn(function()end)) -- no error, just ignored
task.defer(task.spawn,task.spawn(function()end),true) -- errors, might be another bug
local co = coroutine.create(function(...)
	print(...)
	while true do
		print(coroutine.yield())
	end
end)
task.defer(task.spawn,co,1)
task.defer(task.spawn,co,2)

Issue Area: Engine
Issue Type: Other
Impact: Moderate
Frequency: Rarely

2 Likes

Thank you for the report.
We are aware about multiple issues with task.defer and task.delay from earlier reports on DevForum.
We are planning to make some changes in the future to solve or improve these functions and we’ll take into the consideration new cases around dead coroutines that you’ve reported here.

1 Like