Skip to content

Lint: replace the cmdlet ban with a cross-runspace scriptblock check - #3

Merged
HalRiveria merged 1 commit into
mainfrom
fix/lint-cross-runspace-scriptblocks
Oct 28, 2026
Merged

HalRiveria merged 1 commit into
mainfrom
fix/lint-cross-runspace-scriptblocks

Conversation

@HalRiveria-Bot

@HalRiveria-Bot HalRiveria-Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

What was wrong

Lint Rule 7 banned cmdlets in worker-runspace code, on the theory that two fresh runspaces calling a cmdlet for the first time at once can deadlock in Windows PowerShell 5.1. Stress testing shows that theory is wrong. The Full Pipeline hang it was written for came from a scriptblock running on another runspace's thread: a shared System.Lazy factory created by one engine ran on the other engine's thread. Every scriptblock it ran inside (a Where-Object filter, a nested delegate) was queued back to the first engine, which was blocked on the same Lazy. Removing cmdlets only looked like a fix because it removed most of those scriptblock calls. The current caches no longer do this.

What changed

  • Lint Rule 7 no longer bans cmdlets. It now flags a scriptblock crossing into a worker:
    • a scriptblock value in a -JobParams entry, or in a hashtable the -NextJobProvider returns
    • a variable holding a scriptblock passed to a .NET method or constructor from runspace-reachable code (Rule 8 already covers the literal and cast forms)
  • Rule 8's comment, the worker-runspace rules in module-descriptions.md, and two code comments in Controller.ps1 and FileDeletion.ps1 now describe the real mechanism. No behavior changes in the modules. The existing .NET calls stay.

How it was tested

  • Cmdlets: put back in every Full Pipeline worker path on a throwaway branch. 0 hangs in 2,000 runs, one Full Pipeline per fresh process, on fake and real cjxl/magick.
  • Old caches: Lazy caches plus cmdlets hung 116 times in 600 runs. All six process dumps show the same cross-runspace deadlock.
  • Standalone repro: two runspaces, one Lazy. Plain cmdlets in the factory never hang. A factory that runs a scriptblock hangs 5 out of 5 when the owning runspace is blocked.
  • New Rule 7: sample asserts for every shape it flags or skips. Planting three scriptblock leaks in real module code makes it fail, as it should, and current main passes it. Rule 8 still flags all three sites in the old cache code.
  • All 21 local harnesses pass.

Follow-up: #4 (review fixes and a runtime guard, pushed after this was merged).

🤖 Generated with Claude Code

Stress testing showed cmdlets are safe in worker runspaces: with cmdlets
put back in every Full Pipeline worker path, 2,000 one-run-per-process
Full Pipeline runs (fake and real cjxl/magick) never hung. The old hang
came from a scriptblock running on another runspace's thread: a shared
System.Lazy factory created by one engine ran on the other engine's
thread, every scriptblock it ran inside (a Where-Object filter, a nested
delegate) was queued back to the first engine, and that engine was
blocked on the same Lazy. The current caches already avoid this.

Lint Rule 7 no longer bans cmdlets. It now flags a scriptblock crossing
into a worker: a scriptblock value in a -JobParams or -NextJobProvider
hashtable, or a scriptblock variable passed to a .NET method or
constructor from runspace-reachable code (Rule 8 covers the literal and
cast forms). Rule 8's comment, the worker-runspace rules in
module-descriptions.md and two code comments now describe the real
mechanism.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HalRiveria
HalRiveria merged commit dcb313b into main Oct 28, 2026
1 check passed
@HalRiveria
HalRiveria deleted the fix/lint-cross-runspace-scriptblocks branch October 28, 2026 07:20
@HalRiveria-Bot HalRiveria-Bot changed the title Lint: replace the cmdlet ban with a cross-runspace scriptblock check Replace the cmdlet ban with cross-runspace scriptblock guards Oct 28, 2026
@HalRiveria-Bot HalRiveria-Bot changed the title Replace the cmdlet ban with cross-runspace scriptblock guards Lint: replace the cmdlet ban with a cross-runspace scriptblock check Oct 28, 2026
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