Task.cancel crash game and studio

Reproduction Steps
https://www.roblox.com/games/9309716052/task-cancel-bug#!/game-instances

task.cancel bug.rbxl (90.5 KB)

Script
			if SHOULD_CRASH then
				local thread = task.delay(totalPlayTime, SoundSystem.StopSFX, sound.Name, sound.Parent)

				sound.Destroying:Connect(function()
					task.cancel(thread)
				end)
			else
				task.delay(totalPlayTime, SoundSystem.StopSFX, sound.Name, sound.Parent)
			end

it seems like cancelling a thread that is already finished will crash the application

Expected Behavior
for the engine not to crash from the script

Actual Behavior
but the engine crashes from the script

Issue Area: Engine
Issue Type: Crashing
Impact: High
Frequency: Constantly
Date First Experienced: 2022-04-08 00:04:00 (+07:00)
Date Last Experienced: 2022-04-08 00:04:00 (+07:00)
A private message is associated with this bug report

5 Likes

Thanks for the report, looks like it’s actually caused by cancelling a parent thread from a child thread. This should error instead of crashing. I’ll have a fix for it soon.

7 Likes

Can you explain why? I don’t understand how this should be fixed it’s supposed to error

I just ran into this problem as well. I’m really confused why this should be erroring because cancelling the thread is not an issue (in this scenario) until doing it after the thread has finished.

In the given example the thread has not actually finished. The delayed function StopSFX calls Destroy on the sound instance which invokes the Destroying event. This is effectively the same as doing the following:

local bindable = Instance.new("BindableEvent")

local thread = task.delay(1, function ()
  bindable:Fire() -- calls task.cancel on this thread which is currently active
end)

bindable.Event:Connect(function ()
  task.cancel(thread)
end)

Meanwhile the following works as you would expect:

local thread = task.delay(1, print, "Resumed")
task.wait(2)
task.cancel(thread)
print("Finished")

so how would I fix that? I don’t want any errors in my code

Yeah I can get the crash to occur with this tiny code block

task.spawn(function(thread)
    task.cancel(thread)
end, coroutine.running()) -- Parent thread status is set to "normal"
print() --only crashes on the command line + some other places when running extra code after

It’s a weird bug because replacing task.cancel() with coroutine.close() throws an error that normal threads can’t be closed but task.cancel() just crashes

image
That should be the expected result when using task.cancel as well since they function basically identically

Deferred Lua will fix this automatically when using events but for now you can wrap your call to cancel in a call to defer.

task.defer(task.cancel, thread)
1 Like

Hey there - still running into this issue. My testing also confirms the crash doesn’t occur if you attempt to cancel the thread normally, but it does when trying to cancel the thread from a child thread.

This errors normally:

local Thread

Thread = task.defer(function()
    task.cancel(Thread)
    print("Thread has run!")
end)

cannot cancel running thread

However, this crashes studio:

local Thread

Thread = task.defer(function()
    task.spawn(function() task.cancel(Thread) end)
    print("Thread has run!")
end)

Something curious I’ve found is that, for the crash to occur, the thread must have something pending. That’s why I included the print at the end of the function. Otherwise, the crash won’t occur and no errors will happen. This code runs without crashes or errors:

local Thread

Thread = task.defer(function()
    task.spawn(function() task.cancel(Thread) end)
end)

The reason why I use task.defer() by the way, is because if I use task.spawn(), it will not yet have returned by when I try to cancel the thread and Thread will be nil.

As a suggestion/question, why does cancelling a thread within itself have to error? Is there a reason why cancelling a thread within itself can’t just stop the thread as if the thread had hit a return?

A use case I’ve had for cancelling threads within themselves is a loading screen where the user may wait for everything to load (the load happens inside a thread) or press the skip button (which cancels this thread). If they press skip, a function FinishLoading() is run which cancels the loading screen thread, and goes on to the menu. If the loading screen ends, the same function is run. But I can’t do this because the loading thread can’t canel itself, meaning I have to cancel the thread separately and can’t just include it in the FinishLoading() function.

Thank you!

I just found out about the bug, really tedious to deal with. I spent hours debugging! Disabling chunk of codes and a lot of stuff around to see what was causing it. Until I saw it was a bug from the Engine.

Could you guys please fix it. I don’t know how it works in the background, but assuming that coroutine.close does the same EXACT thing as task.cancel. I don’t know why is taking long to be fixed. I would assume it got pushed because coroutine.close exists.

If there is any other reason behind the fix taking long, I don’t want any response from anyone telling me that is not as easy as I expect.

Code Producing The Bug:

local Thread = coroutine.running()
local Timer = task.delay(10, function()
    task.spawn(Thread)
end)
coroutine.yield()
task.cancel(Timer)

Hi, just wanted to bump to say I ran into this funny issue today as well! Any updates on fix (i.e. error instead of crash)? @WallsAreForClimbing

5 Likes

There is a fix for this but we want to explore whether we can eliminate the error entirely and make this cancel all threads before locking in the behavior.

For now, you should use coroutine.close

2 Likes