Skip to content

Guard against leaking threads while restarting Shake - #38

Draft
soulomoon wants to merge 13 commits into
masterfrom
codex/pr-5027-scope-ownership
Draft

Guard against leaking threads while restarting Shake#38
soulomoon wants to merge 13 commits into
masterfrom
codex/pr-5027-scope-ownership

Conversation

@soulomoon

@soulomoon soulomoon commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Alternative ownership model for haskell/haskell-language-server#5027.

Summary

  • close an AIO scope once cleanup begins so late children cannot register into a drained scope
  • make admission explicit: admitted children run their bodies, while refused children skip the work and finish normally
  • leave cancellation of admitted threads to scope cleanup, including a parent whose selected child is later refused
  • cover the upstream out-of-scope living-async failure through two real build calls

Why

A force runner can select a Running force while its AIO scope is open, then remain parked until after cleanup closes that scope. After the build step advances, viewDirty prevents deliberate reuse of the old Running force, so the race is limited to work that selected the force before teardown.

Upstream allows that selected force to register a child into the already-drained scope. The build waiting on the child can then be cancelled while the child remains alive with no scope left to own or cancel it.

This change assigns one cancellation owner to each outcome: cleanup cancels admitted threads; refused threads never enter the registry, do not execute their bodies, and finish normally. The already-admitted parent waits for external scope cancellation instead of trying to kill the same work a second time.

Regression test

The test makes the out-of-scope lifetime deterministic:

  1. The first build publishes a lazy Running force but remains in key traversal.
  2. A second build selects that force and parks before forcing it.
  3. Cancelling the first build closes the force's captured AIO scope.
  4. The second build resumes, starts the late rule async, and waits on it.
  5. Cancelling the second build reveals whether that child is still alive outside any owning scope.

With upstream master at b7e5d2526, the late child remains alive and the test fails with expected: False / but got: True for outOfScopeAlive. The test releases and joins that orphan before reporting RED, so the test itself leaks nothing. With this branch, admission is refused before the rule body starts and no out-of-scope child exists.

Validation

  • RED on upstream: cabal test hls-graph:test:tests -fpedantic --test-show-details=direct --test-option=-p --test-option='/does not leave a late async/'
  • GREEN here: cabal test hls-graph:test:tests -fpedantic --test-show-details=direct
  • all 11 hls-graph tests passed
  • git diff --check

The branch is rebased onto upstream master at b7e5d2526.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants