task.wait() does not respect script.Disabled and always resumes

Reproduction Steps
task.wait() does not respect script.Disabled like wait() does, so I cannot use it since it means my code just continues running when I have explicitly tried to halt execution.

Repro and demonstration:
wait_disable.rbxlx (37.7 KB)

Two scripts:

-----------------------------------------------------------------------------------
-- ScriptA
-----------------------------------------------------------------------------------

task.delay(3, function()
	warn("Disabling wait script!")
	script.Disabled = true
end)


for i=1, 15 do
	print(string.format(
		"% 9s % 3d | Disabled: %s",
		"wait", i, tostring(script.Disabled)
	))
	wait(0.5)
end

warn("wait will not reach this since it gets disabled before the loop finished")

-----------------------------------------------------------------------------------
-- ScriptB
-----------------------------------------------------------------------------------

task.delay(3, function()
	warn("Disabling task.wait script!")
	script.Disabled = true
end)


for i=1, 15 do
	print(string.format(
		"% 9s % 3d | Disabled: %s",
		"task.wait", i, tostring(script.Disabled)
		))
	task.wait(0.5)
end

warn("task.wait finished regardless of the script being disabled")

Output:

       wait   1 | Disabled: false - ScriptA:8
  task.wait   1 | Disabled: false - ScriptB:8
       wait   2 | Disabled: false - ScriptA:8
  task.wait   2 | Disabled: false - ScriptB:8
       wait   3 | Disabled: false - ScriptA:8
  task.wait   3 | Disabled: false - ScriptB:8
       wait   4 | Disabled: false - ScriptA:8
  task.wait   4 | Disabled: false - ScriptB:8
       wait   5 | Disabled: false - ScriptA:8
  task.wait   5 | Disabled: false - ScriptB:8
       wait   6 | Disabled: false - ScriptA:8
  task.wait   6 | Disabled: false - ScriptB:8
  Disabling wait script! - ScriptA:2
  Disabling task.wait script! - ScriptB:2
  task.wait   7 | Disabled: true - ScriptB:8
  task.wait   8 | Disabled: true - ScriptB:8
  task.wait   9 | Disabled: true - ScriptB:8
  task.wait  10 | Disabled: true - ScriptB:8
  task.wait  11 | Disabled: true - ScriptB:8
  task.wait  12 | Disabled: true - ScriptB:8
  task.wait  13 | Disabled: true - ScriptB:8
  task.wait  14 | Disabled: true - ScriptB:8
  task.wait  15 | Disabled: true - ScriptB:8
  task.wait finished regardless of the script being disabled - ScriptB:15

Expected Behavior
I expect task.wait() to stop executing my loop when the script is Disabled, like the behavior of the wait() it is supposed to be a replacement for.

Actual Behavior
task.wait() will always resume.

Workaround
Use the gross old wait().

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

32 Likes

This is very odd and from what I know should not happen perhaps it’s the task.delay() causing this?

The behaviour of wait() is not consistent.

script.Disabled = true
for i = 1, 15 do
    print(i)
    wait(0.5)
end
warn("This should not run")

This still warns.

spawn(function() script.Disabled = true end)
for i = 1, 15 do
    print(i)
    wait(0.5)
end
warn("This should not run")

But this does not.

task.wait() would warn in both cases.

6 Likes

Thanks for the report.

I’ve taken a look into this and it’s not guaranteed to behave as you are expecting. This is why there’s some inconsistency with it working in some cases and not others, and not at all with the new wait method.

Still, I can see why this would be useful so I’ll follow-up internally on this. In the meantime, getting a better understanding of what you’re trying to do would definitely help.

14 Likes

This also occurs if the script is destroyed. The code will continue to run even if the script is disabled or destroyed, which can leak memory or cause unintended behavior.

A case where I think I would run into problems with this is with client character ability code. Some of my abilities have infinite loops that raycast regularly or do other processing. If the character dies and I have one such script running in their character, the loop will never stop and rapidly start running duplicates as the player respawns (assuming the code doesn’t error out). This is extremely unexpected for me as a developer, and that I have to manually handle this is very unintuitive.

@WallsAreForClimbing (Forgot to reply)

16 Likes

I don’t think any developer expects a script to run any threads when script.Disabled is set to true, so this feels like a pretty important thing to take a look at. When I disable a script or a script is in nil I want to be 100% sure that it won’t be running anymore, I feel like this has a lot of potential to cause unwanted memory leaks without much explanation.

14 Likes

I just spent a couple of hours de bugging reasons for why my code suddenly started running while dead - and turns out it’s due to @tnavarts 's signal API it actually seems to be something different and confusing to debug.

warn(debug.traceback())
warn(script:GetFullName())

the top prints Workspace.[CHARACTER].[SCRIPT], and yet the other only prints [CHARACTER].[SCRIPT], without Workspace. to show that it’s ran within the DataModel. Unsure of what to make of this.

Adding onto this, I honestly would’ve probably gone insane if not for someone pointing me to this thread, because I could’ve never guessed that the issue was related to signals (at least, in my case). I even wrote a script cleanup script to destroy all scripts in the character upon respawn, and yet even that didn’t help, which caused great confusion.

local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer

LocalPlayer.CharacterRemoving:Connect(function(Character)
	for _, Script in next, Character:GetDescendants() do
		if Script:IsA("LuaSourceContainer") then
			Script:Destroy()
			print("Destroyed", Script:GetFullName())
		end
	end
end)

This is a major break in otherwise expected behavior, and should most definitely be addressed.

1 Like

Any follow up on this? This really can mess with scripts as we were advised to use task.wait yet we fall back on wait because of this.

2 Likes

I don’t feel safe using task.wait because of this issue.

2 Likes

Hello, everyone! I sent this issue to the engineers and let them know that this issue still occurs. Will update its status once they are done with it. Have a nice day!

12 Likes

Thank you! Hope this gets fixed!