Fix concurrent-import crash in Run.Parallel (thread-unsafe verb patch)#2901
Merged
Conversation
VerbsPatcher.AllowShouldVerb reaches into PowerShell's process-global System.Management.Automation.Verbs verb dictionary and mutates it. That Dictionary is not thread-safe, so parallel worker Import-Module calls in Run.Parallel could corrupt it and throw IndexOutOfRange in set_Item. Serialize both the insert and the delayed removal behind a process-global lock and guard the insert with a ContainsKey check so only the first import mutates the shared dictionary. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reading the shared Dictionary with an unlocked ContainsKey before taking the lock would race with the guarded insert (a resize could tear the bucket array during a concurrent read), so document that the lock-only form is intentional and that a correct fast path would need a separate volatile flag. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
nohwnd
added a commit
that referenced
this pull request
Jul 18, 2026
#2901) (#2903) * Fix concurrent-import crash in Run.Parallel (thread-unsafe verb patch) VerbsPatcher.AllowShouldVerb reaches into PowerShell's process-global System.Management.Automation.Verbs verb dictionary and mutates it. That Dictionary is not thread-safe, so parallel worker Import-Module calls in Run.Parallel could corrupt it and throw IndexOutOfRange in set_Item. Serialize both the insert and the delayed removal behind a process-global lock and guard the insert with a ContainsKey check so only the first import mutates the shared dictionary. * Explain why AllowShouldVerb does not double-check outside the lock Reading the shared Dictionary with an unlocked ContainsKey before taking the lock would race with the guarded insert (a resize could tear the bucket array during a concurrent read), so document that the lock-only form is intentional and that a correct fast path would need a separate volatile flag. --------- (cherry picked from commit 8b05c3d) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Run.Parallelruns every test file in its ownForEach-Object -Parallelrunspace, but those runspaces share one process and the same loaded assemblies. On import Pester calls[Pester.VerbsPatcher]::AllowShouldVerb(...), which reflects into PowerShell's process-globalSystem.Management.Automation.Verbs.s_validVerbsdictionary and doesvalidVerbs["Should"] = true(plus a background task that removes it after 5 seconds).Dictionary<TKey,TValue>is not thread-safe, so when several workersImport-Module Pesterat the same time the concurrent writes tear the internal bucket array and throw:That kills the parallel
Invoke-Pester, no[Pester.Run]object comes back, andtst/Pester.RSpec.Parallel.ts.ps1then fails on Windows PS7 with follow-on errors likeproperty TotalCount cannot be foundorproperty Containers cannot be found. It is Windows-only and intermittent, but it is not flakiness, it is an unsynchronized write to a shared dictionary, and it reproduces onmain.The fix is in
VerbsPatcher.csonly.Pester.dllloads once per process, so a static lock is shared across all runspaces. I wrap both the insert and the delayed removal inlock (s_lock)and guard them withContainsKey, so the first import mutates the dictionary and the rest are no-ops. Noawaitinside the lock.Not
Dictionary.TryAdd, it does not exist on the net462 target. Not a bare retry/catch, a torn dictionary is permanently corrupt, not transiently retryable.Verified:
build.ps1 -Cleanis clean,test.ps1 -File tst/Pester.RSpec.Parallel.ts.ps1 -SkipPTestspasses (22), and1..8 | ForEach-Object -Parallel { Import-Module ./bin/Pester.psd1 } -ThrottleLimit 8no longer crashes.