Skip to content

fix(ap): cancel the flows a fork draws from before its parks - #7

Merged
yogthos merged 1 commit into
mainfrom
fix/ap-cancel-forked-flow
Sep 7, 2026
Merged

yogthos merged 1 commit into
mainfrom
fix/ap-cancel-forked-flow

Conversation

@yogthos

@yogthos yogthos commented Sep 7, 2026

Copy link
Copy Markdown
Member

Cancelling an ap cancelled its pending parks first and its forked flows second. Cancelling a park can resume the branch synchronously on the owner fiber: the branch fails at its ? and pumps, the pump pulls the next value from a flow that is still live and forks again, the new park is cancelled on arrival, and control never reaches the loop that would have cancelled the flow.

Over an infinite seed the process spun forever inside cancel at full CPU and the consumer never settled; over a finite seed it drained the whole seed before stopping.

Found through samizdat's supervisor stream, a reduce over (ap (let [_ (?> (seed (repeat nil)))] (? (sleep ms)) (drain))): stopping it measured 88,641 iterations in the two seconds after the cancel, with the reduce never settling.

Fix: cancel the flows first, then the parks, as Ambiguous.java cancels its choice ring before its token. With the flow cancelled the next pull terminates the choice and the pump runs dry.

Pinned by ebb.ap-cancel-seed-test: the cancel reaches the seed, no further fork runs, and the reduce settles with Cancelled. Full suite: 285 tests, 1207 assertions, 0 failures.

Cancelling an ap cancelled its pending parks first and its forked flows
second. Cancelling a park can resume the branch synchronously on the owner
fiber: the branch fails at its `?` and pumps, the pump pulls the next value
from a flow that is still live and forks again, the new park is cancelled on
arrival, and control never reaches the loop that would have cancelled the
flow. Over an infinite seed the process spun forever inside `cancel` at full
CPU and the consumer never settled; over a finite seed it drained the whole
seed before stopping.

Found through samizdat's supervisor stream, a reduce over
(ap (let [_ (?> (seed (repeat nil)))] (? (sleep ms)) (drain))): stopping it
measured 88,641 iterations in the two seconds after the cancel.

Cancel the flows first, then the parks, as Ambiguous.java cancels its choice
ring before its token. With the flow cancelled, the next pull terminates the
choice and the pump runs dry. Pinned by ebb.ap-cancel-seed-test: the cancel
reaches the seed, no further fork runs, and the reduce settles with Cancelled.
@yogthos
yogthos merged commit 5f6a4f3 into main Sep 7, 2026
4 checks passed
@yogthos
yogthos deleted the fix/ap-cancel-forked-flow branch September 7, 2026 01:29
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