Repository navigation
Give each runspace in a RunspacePool its own InitialSessionState - #28286
Open
Joseph "Ryan" Ries (ryanriesMSFT) wants to merge 1 commit into
Open
Joseph "Ryan" Ries (ryanriesMSFT) wants to merge 1 commit into
Joseph "Ryan" Ries (ryanriesMSFT) wants to merge 1 commit into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
RunspacePoolInternal created every runspace with CreateRunspaceFromSessionStateNoClone, so all runspaces in a pool shared a single InitialSessionState instance. The InitialSessionState is updated when a runspace is opened and when modules are imported into it (for example, the type and format files that were loaded are recorded in its Types and Formats collections), and Import-Module uses those collections to decide whether a module's TypesToProcess and FormatsToProcess files are already loaded. As a result, a runspace that was opened before another runspace in the same pool imported a module skipped that module's type and format data when it imported the module itself. ETS members such as the enum script properties generated for CDXML modules (NetConnection, Storage, ...) were missing in that runspace, so properties like NetworkCategory surfaced as raw integers. Whether a given runspace was affected depended on runspace creation order and timing. Create pooled runspaces with RunspaceFactory.CreateRunspace, which clones the pool's InitialSessionState exactly like a standalone runspace, and remove the now-unused no-clone code path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7411f14d-b619-4f89-9a9e-b0880f11ef33
Joseph "Ryan" Ries (ryanriesMSFT)
force-pushed
the
fix-runspacepool-per-runspace-iss
branch
from
October 29, 2026 20:36
94e152f to
b3a7e4b
Compare
Joseph "Ryan" Ries (ryanriesMSFT)
marked this pull request as ready for review
October 29, 2026 20:39
Joseph "Ryan" Ries (ryanriesMSFT)
requested a review
from a team
as a code owner
October 29, 2026 20:39
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues were identified for the change isolating each pooled runspace’s session state.
No open issues 🎉
What changed in this PR
This PR isolates each local pooled runspace’s InitialSessionState, preventing module imports in one runspace from causing another to skip type and format data.
Changes:
- Use the standard cloning factory when creating pooled runspaces.
- Remove the unused internal factory and constructors that bypassed cloning.
- Add a deterministic, cross-platform regression test covering type and format data loading in two pooled runspaces.
| File | Description |
|---|---|
test/powershell/engine/Remoting/RunspacePool.Tests.ps1 |
Adds event-coordinated regression coverage for per-runspace module data. |
src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs |
Creates each runspace with its own session-state copy. |
src/System.Management.Automation/engine/hostifaces/LocalConnection.cs |
Removes the constructor supporting clone suppression. |
src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs |
Removes the unused no-clone factory method. |
src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs |
Removes the base constructor supporting clone suppression. |
Review effort: Balanced
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.
PR Summary
Give each runspace in a local
RunspacePoolits own copy of the pool'sInitialSessionState, instead of sharing a single instance across all runspaces in the pool.Sharing the instance can make a pooled runspace silently skip a module's type and format data (
TypesToProcess/FormatsToProcess). ETS members are then missing in that runspace. For example, the enum script properties that CDXML modules generate are missing, so(Get-NetConnectionProfile).NetworkCategoryintermittently returns[uint32] 2instead ofDomainAuthenticated. The same happens withGet-Disk,Get-Partition, and other cmdlets whose output relies on module type data, whenever they run in a pooled runspace.Changes:
RunspacePoolInternal.CreateRunspace()now creates runspaces withRunspaceFactory.CreateRunspace(host, _initialSessionState), which clones theInitialSessionStatejust like a standalone runspace.RunspaceFactory.CreateRunspaceFromSessionStateNoClone, theLocalRunspace(..., bool suppressClone)constructor, and theRunspaceBase(..., bool suppressClone)constructor. All three are internal.CItest inRunspacePool.Tests.ps1fails without this change and passes with it.PR Context
RunspacePoolInternal.CreateRunspace()created every runspace withCreateRunspaceFromSessionStateNoClone, so all runspaces in a pool shared oneInitialSessionStateobject. That object behaves as mutable, per-runspace state:InitialSessionState.Bind(..., updateOnly: true)→UpdateTypes/UpdateFormatsloads the module's type and format files into the importing runspace'sTypeTableand format database. It also appends them tocontext.InitialSessionState.Types/.Formats, which is the shared instance.ModuleCmdletBase.LoadModuleManifestdecides whether a manifest'sTypesToProcess/FormatsToProcessfile is already loaded by looking it up inContext.InitialSessionState.Types/.Formats. If the file is listed, the binding is skipped.Each runspace still has its own
TypeTableand format database, which are populated from theInitialSessionStatewhen the runspace is opened. So a failure follows when both of these happen:InitialSessionState.When B then imports the same module, the files are already listed, so B never loads them. Member lookup falls back to the CIM/.NET adapter and returns the raw value. No error or warning is written.
The problem is intermittent and only shows up in some hosts because it requires all of the following:
RunspaceFactory.CreateRunspacePool(min, max, ...), by custom hosts, or (by code inspection) by remoting endpoints when the client opens a pool with more than one runspace;Code paths that create standalone runspaces already clone the
InitialSessionStateand are not affected. This includesRunspaceFactory.CreateRunspace(...), the console host, andForEach-Object -Parallel.Not sharing the instance also removes other cross-runspace effects of the shared object:
Types/Formats(UpdateTypesusesParallel.ForEach) while a sibling appends to them duringImport-Module.Remove-Module,Update-TypeData, andRemove-TypeDatain one runspace clear and rebuild the shared lists.InitialSessionStatewith a sharedTypeTableentry, only the first runspace actually shared theTypeTable, becauseUpdateTypesreplaces that entry in the shared instance.Cost: one
InitialSessionState.Clone()per runspace that the pool creates. ForInitialSessionState.CreateDefault()that is about 1 ms, compared with about 50 ms to open a runspace.Windows repro. Without the fix, the second line typically shows
0 (UInt32)or the corresponding integer:The new cross-platform test reproduces the same ordering deterministically with a self-contained module that has a types file and a format file. It uses events instead of sleeps. Results:
Expected 42, because the type data should be loaded in the 'Second' runspace, but got $null.Locally, I also ran the existing tests that use runspace pools, along with the
Import-Module,Update-TypeData,Remove-TypeData, type-data, and runspace cmdlet tests. Only the WinRM-based remote pool test failed, because WinRM isn't configured on the test machine.PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header