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

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.

4 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!

16 Likes

Thank you! Hope this gets fixed!

This still isn’t fixed, the health regeneration script in my game kept running even after the character died, resulting in thousands of loops running at once dropping the server framerate down to under 20.

8 Likes

Is this issue actively being looked into? I’ve already seen quite a few developers fall into the pitfall of thinking scripts will stop when they’re destroyed, and having massive memory leaks that are hard to diagnose.

A better workaround is to use the heartbeat wait instead of wait() until this is fixed

Doesn’t heartbeat not work with FPS unlockers though? And it updates way more frequently then what the developer wants

Also I have encountered this bug myself, and I found it to be really annoying.

There are now two way to create immortal script

1-

2-

3 Likes

That’s the case for task.wait as well, and any RunService event, as they’re all just fired one after another.

I’m not entirely sure if this has any issues, but this <should> work just fine, it’s better than using a Heartbeat loop with Wait, as that would be 3x slower in Deferred mode, and it’s even worse comparing with Immediate mode.

local function Wait(n: number?): number
    n = if typeof(n) == 'number'
        and n
        or 0

    local timePassed = 0

    local thread = coroutine.running()
    local connection
    connection = RunService.Heartbeat:Connect(function(deltaTime)
        timePassed += deltaTime

        if timePassed >= n then
            connection:Disconnect()
            task.spawn(thread, timePassed)
        end
    end)

    return coroutine.yield()
end

Note that this will have different Wait deltaTime returns than task.wait, because it respects frame time and not actual spent time, this is what you want for visual effects, though using RenderStepped is a better option in that case.


For me this isn’t actually that big of a deal, I can understand how it can be an issue for people with older code and thousands of lines using while true do task.wait() loops, but I think you can take some time to change that to use RunService connections and manage the time passed yourself instead, then it shouldn’t take too long.

local TimePassedSinceLastUpdate = 0
RunService.Heartbeat:Connect(function(deltaTime)
    TimePassedSinceLastUpdate += deltaTime

    if TimePassedSinceLastUpdate < 0.25 then
        return
    end

    -- Code that runs every 0.25 seconds.
end)

RunService connections are disconnected when a script dies. I believe it identifies the script using functions, so it’s a much more modern and better option.

2 Likes

Not sure if this may work but every time a loop runs you could check if the script is disabled or not. If it is, then it breaks / disconnects.

We’ve identified a fix for this, it’ll be a little while until we ship it but when we do I’ll be sure to give you all another update.

15 Likes

Will this fix also affect the original wait() and its identified inconsistent behaviour?

This change will only affect the task library

4 Likes

while this is still not fixed, I suggest using coroutine instead, result from my test task.wait() seems to respect coroutine.close(), but keep in mind RBXScriptConnection does not respect coroutine.close().

Potentially resolved?

Delayed threads (threads scheduled with task.delay or task.wait) will no longer be resumed in disabled scripts

https://developer.roblox.com/en-us/resources/release-note/Release-Notes-for-514

2 Likes

Yes. I’ll follow-up on this thread once it’s live

5 Likes

Thanks for fixing this. I’ve had to use code like this:

local function EnableThreads()
	CC = true
end
local function KillThreads()
	CC = false
end
local function ScriptUpdated()
	if (script.Disabled == true) then
		KillThreads()
	elseif (script.Disabled == false) then
		EnableThreads()
	end
end
local ScriptDestroyingConection:RBXScriptConnection;
local ScriptDisabledConnection:RBXScriptConnection;
local ActorDestroyingConnection:RBXScriptConnection;
local function GC()
	KillThreads()
	if (Actor ~= nil) and (Actor:IsA("Actor") == true) then
		Actor:Destroy()
	end
	if (ScriptDestroyingConection) then
		ScriptDestroyingConection:Disconnect()
	end
	if (ScriptDisabledConnection) then
		ScriptDisabledConnection:Disconnect()
	end
	if (ActorDestroyingConnection) then
		ActorDestroyingConnection:Disconnect()
	end
	script:Destroy()
end
ScriptDestroyingConection = script.Destroying:Connect(GC)
ScriptDisabledConnection = script.Changed:Connect(KillThreads)
ActorDestroyingConnection = Actor.Destroying:Connect(GC)
...
while (CC == true) and (task.wait()) do
...

The fix for this is now live.

23 Likes

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