Skip to content

🐛 Unify halt around signal + observe scope.destroyed (#1159, #1160)#1167

Draft
cowboyd wants to merge 2 commits into
v4from
cl/halt-unification
Draft

🐛 Unify halt around signal + observe scope.destroyed (#1159, #1160)#1167
cowboyd wants to merge 2 commits into
v4from
cl/halt-unification

Conversation

@cowboyd
Copy link
Copy Markdown
Member

@cowboyd cowboyd commented May 14, 2026

Motivation

Fix #1159 and #1160

task.halt() had two surfaces (Promise and Operation), and the Promise surface spawned a second routine via owner.run(destroy) to drive teardown. This second task raced the original task's own routine for control of the same delimiter. This resulted in incorrect teardown sequencing.

#1153 and #1154 remain open — they're a different class of fix (sticky-return semantics + capturing iter.return's done value), substantially easier on this substrate but out of scope here.

Approach

task.halt() collapses to a single signal-and-observe pair. We lazily call top.interrupt() and then join on scope.destroyed. The task's own routine drives unwinding via its existing try { yield* top } finally { yield* destroy() }, so all the work happens in the original routine. With one pathway, there is no possibility for a race.

Alternates

We tried keeping both surfaces with #1155, and just "synchronize them harder", but the problem is the secord routine itself.

TODO:

Future Direction

Eventually, We'll actually move the signal and halting logic entirely into the scope, using an async like abort signal, so that task.halt() just becomes lazy signal.abort()

cowboyd added 2 commits May 14, 2026 11:55
In order to implement critical sections that cannot be interrupted and
that are also able to implement sticky return, we need to make the
behavior of the reducer more context sensitive.

This drops the whole validation behavior and instead completely
inverts control over iteration to the delimiter from the reducer. Now,
the reducer becomes a pure iterator-stepping engine. The delimiter can
tell it to use "next", "return", "throw" to tell it how to advance the
iterator, or it can even tell it "drop" to ignore the dequeued item
entirely.
There were multiple pathways for signaling a halt and then awaiting
its consequence. This unifies all of them by interrupting the current
delimiter, and then awaiting a single future representing the
destruction of the scope.

Now, `task.halt()` collapses to this pattern.

This eliminates the dual-routine pattern that gave rise to both #1159
and #1160
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 14, 2026

Open in StackBlitz

npm i https://pkg.pr.new/effection@1167

commit: c780092

Base automatically changed from cl/invert-reducer to v4 May 14, 2026 20:05
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.

1 participant