fix: check if cleanup functions disposed effect

This commit is contained in:
littensy 2026-03-30 04:11:57 -07:00
parent 1f4c7c7a65
commit 4a48dacfbd
3 changed files with 85 additions and 1 deletions

View file

@ -202,6 +202,10 @@ local function run(effect: EffectNode)
if bit32.btest(flags, DIRTY) or (bit32.btest(flags, PENDING) and checkDirty(effect.deps :: Link, effect)) then if bit32.btest(flags, DIRTY) or (bit32.btest(flags, PENDING) and checkDirty(effect.deps :: Link, effect)) then
runCleanups(effect.cleanups) runCleanups(effect.cleanups)
if effect.flags == NONE then
return
end
cycle += 1 cycle += 1
effect.depsTail = nil effect.depsTail = nil
effect.flags = WATCHING effect.flags = WATCHING
@ -217,6 +221,10 @@ local function run(effect: EffectNode)
elseif result then elseif result then
table.insert(effect.cleanups, noYield(result)) table.insert(effect.cleanups, noYield(result))
end end
if effect.flags == NONE then
runCleanups(effect.cleanups)
end
else else
effect.flags = WATCHING effect.flags = WATCHING
end end

View file

@ -375,4 +375,80 @@ test("infinite recursion should eventually throw", function()
assert(not success, "should have thrown") assert(not success, "should have thrown")
end) end)
test("should not retrigger the effect if the cleanup modifies one of the dependencies", function()
local a, setA = signal(0)
local calls = {}
effect(function()
table.insert(calls, a())
return function()
setA(2)
end
end)
assert(#calls == 1, "should have run once initially")
table.clear(calls)
setA(1)
assert(#calls == 1, "should run once after an update")
assert(calls[1] == 2, `bad call: {calls[1]}`)
end)
test("should run the cleanup if the effect disposes itself", function()
local a, setA = signal(0)
local calls = 0
local dispose
dispose = effect(function()
if a() > 0 then
dispose()
return function()
calls += 1
end
end
end)
assert(calls == 0, "should not have called cleanup initially")
setA(1)
assert(calls == 1, "should have called cleanup once after first update")
setA(2)
assert(calls == 1, "should not call cleanup again after second update")
end)
test("should not run the effect if the cleanup function disposes it", function()
local a, setA = signal(0)
local calls = 0
local dispose
dispose = effect(function()
a()
calls += 1
return function()
dispose()
end
end)
assert(calls == 1, "should have run effect once initially")
setA(1)
assert(calls == 1, "should not run effect again after update")
end)
test("should reset the cleanup if the effect throws", function()
local a, setA = signal(0)
local calls = 0
effect(function()
if a() == 0 then
return function()
calls += 1
end
else
error("hello")
end
end)
assert(calls == 0, "should not have called cleanup initially")
local success, err = pcall(setA, 1)
assert(not success and string.find(tostring(err), "hello"), `bad error: {err}`)
assert(calls == 1, "should have called cleanup once after throwing")
setA(0)
assert(calls == 1, "should not call cleanup again after resetting value")
end)
return {} return {}

View file

@ -3,5 +3,5 @@ wally = "UpliftGames/wally@0.3.2"
rojo = "rojo-rbx/rojo@7.6.1" rojo = "rojo-rbx/rojo@7.6.1"
stylua = "johnnymorganz/stylua@2.3.1" stylua = "johnnymorganz/stylua@2.3.1"
selene = "Kampfkarren/selene@0.30.0" selene = "Kampfkarren/selene@0.30.0"
luau-lsp = "johnnymorganz/luau-lsp@1.62.0" luau-lsp = "johnnymorganz/luau-lsp@1.63.0"
lute = "luau-lang/lute@0.1.0-nightly.20260219" lute = "luau-lang/lute@0.1.0-nightly.20260219"