Skip to content

Fix scope stack staying broken after a yield in strict mode - #105

Merged
centau merged 3 commits into
centau:mainfrom
kaan650:fix-yield-scope-stack
Oct 22, 2026
Merged

centau merged 3 commits into
centau:mainfrom
kaan650:fix-yield-scope-stack

Conversation

@kaan650

@kaan650 kaan650 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

When strict mode catches a yield in a reactive scope, the update stops but the scopes pushed inside the yielded thread stay on the scope stack. A yield in a show() component leaves the show() effect on the stack, so its next update errors as if it was still running and the root can't be destroyed:

vide.strict = true

local visible = source(false)
local destroy = root(function()
    show(visible, function()
        task.wait()
        return create "Frame" {}
    end)
end)

visible(true) -- attempt to yield in reactive scope
visible(false) -- a scope, that should rerun due to the update of a source, is already active
destroy() -- attempt to destroy an active scope

Strict mode now warns on a yield instead of erroring (print outside Roblox) and leaves the thread to finish on its own. The thread pops the scopes it pushed once it's resumed, so the caller's scope is moved above them for the caller to pop. The rest of the update carries on, so the component above shows up after task.wait() like it does without strict mode. An error after the thread resumes is rethrown from the thread, since there is no caller left to return it to.

@centau

centau commented Oct 22, 2026

Copy link
Copy Markdown
Owner

I have concerns with cancelling the thread:

  • If a yield occurs within vide.untrack() and then the thread is cancelled, node.effect is never restored and the node remains untrackable.
  • If a yield occurs within vide.batch() and then the thread is cancelled, flags.batch is is never removed and future source updates would never occur unless vide.batch() is called again.
  • See Recommendation of coroutine.close() and task.cancel() #110

I'd be satisfied with just emitting a warn (print if running in a vanilla Luau environment) if a yield is detected. I don't think we need a complete restoration (if that's even possible?). This should be a rare enough user-error that the UX isn't harmed

@kaan650
kaan650 force-pushed the fix-yield-scope-stack branch from 6b7bbca to beba14b Compare October 22, 2026 12:34
@kaan650

kaan650 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Makes sense, thanks. I dropped the cancel and it warns now (print outside Roblox). Nothing is restored: the thread pops its own scopes when it's resumed, the caller's scope just gets moved above them so the caller pops the right one. untrack/batch restore themselves once the thread resumes, and a component with task.wait() shows up after the wait like without strict mode. A thread that is never resumed keeps its scope on the stack, same as a yield without strict mode.

Since yields don't error anymore, I renamed the two error on ... yield tests to warn on ... and changed the line in the strict mode docs. Also fixed a type error #100 left in try_evaluate_node (return false, new_value needed a cast).

@centau
centau merged commit 033d30f into centau:main Oct 22, 2026
1 check passed
@centau

centau commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants