Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,52 +102,6 @@ protected RunspaceBase(PSHost host, InitialSessionState initialSessionState)
this.ApartmentState = initialSessionState.ApartmentState;
}

/// <summary>
/// Construct an instance of an Runspace using a custom
/// implementation of PSHost.
/// </summary>
/// <param name="host">
/// The explicit PSHost implementation
/// </param>
/// <param name="initialSessionState">
/// configuration information for this runspace instance.
/// </param>
/// <param name="suppressClone">
/// If true, don't make a copy of the initial session state object.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Host is null.
/// </exception>
/// <exception cref="System.ArgumentNullException">
/// host is null.
/// </exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "OK to call ThreadOptions")]
protected RunspaceBase(PSHost host, InitialSessionState initialSessionState, bool suppressClone)
{
if (host == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(host));
}

if (initialSessionState == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState));
}

Host = host;
if (suppressClone)
{
InitialSessionState = initialSessionState;
}
else
{
InitialSessionState = initialSessionState.Clone();
}

this.ThreadOptions = initialSessionState.ThreadOptions;
this.ApartmentState = initialSessionState.ApartmentState;
}

/// <summary>
/// The host implemented PSHost interface.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,40 +127,6 @@ public static Runspace CreateRunspace(PSHost host, InitialSessionState initialSe
return new LocalRunspace(host, initialSessionState);
}

/// <summary>
/// Creates a runspace using specified PSHost and InitialSessionState.
/// </summary>
/// <param name="host">
/// Host implementation for runspace.
/// </param>
/// <param name="initialSessionState">
/// InitialSessionState information for the runspace.
/// </param>
/// <returns>
/// A runspace object
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when host is null
/// </exception>
/// <exception cref="ArgumentNullException">
/// Thrown when initialSessionState is null
/// </exception>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")]
internal static Runspace CreateRunspaceFromSessionStateNoClone(PSHost host, InitialSessionState initialSessionState)
{
if (host == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(host));
}

if (initialSessionState == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState));
}

return new LocalRunspace(host, initialSessionState, true);
}

#endregion

#region RunspacePool Factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,6 @@ internal sealed partial class LocalRunspace : RunspaceBase
{
#region constructors

/// <summary>
/// Construct an instance of an Runspace using a custom implementation
/// of PSHost.
/// </summary>
/// <param name="host">
/// The explicit PSHost implementation
/// </param>
/// <param name="initialSessionState">
/// configuration information for this minshell.
/// </param>
/// <param name="suppressClone">
/// If true, don't make a copy of the initial session state object
/// </param>
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")]
internal LocalRunspace(PSHost host, InitialSessionState initialSessionState, bool suppressClone)
: base(host, initialSessionState, suppressClone)
{
}

/// <summary>
/// Construct an instance of an Runspace using a custom implementation
/// of PSHost.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,11 @@ protected Runspace CreateRunspace()
Dbg.Assert(_initialSessionState != null, "_initialSessionState should not be null");
// TODO: exceptions thrown here need to be documented
// runspace.Open() did not document all the exceptions.
Runspace result = RunspaceFactory.CreateRunspaceFromSessionStateNoClone(host, _initialSessionState);

// Each runspace gets its own copy of the InitialSessionState: opening a runspace and importing modules
// into it update the InitialSessionState (e.g. the loaded type and format files), so it must not be
// shared with the other runspaces in the pool.
Runspace result = RunspaceFactory.CreateRunspace(host, _initialSessionState);

result.ThreadOptions = this.ThreadOptions == PSThreadOptions.Default ? PSThreadOptions.ReuseThread : this.ThreadOptions;
result.ApartmentState = this.ApartmentState;
Expand Down
124 changes: 124 additions & 0 deletions test/powershell/engine/Remoting/RunspacePool.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,127 @@ Describe "Remote runspace pool should expose commands in endpoint configuration"
$r4.Name | Should -BeExactly 'Get-CimInstance'
}
}

Describe "Local runspace pool should load module type and format data in every runspace" -Tags 'CI' {

BeforeAll {
$moduleName = 'RunspacePoolTypeDataTest'
$typeName = 'RunspacePoolTypeDataTest.Item'
$moduleDir = Join-Path $TestDrive $moduleName
$null = New-Item -ItemType Directory -Path $moduleDir -Force

Set-Content -Path (Join-Path $moduleDir "$moduleName.types.ps1xml") -Value @"
<Types>
<Type>
<Name>$typeName</Name>
<Members>
<ScriptProperty>
<Name>Doubled</Name>
<GetScriptBlock>`$this.Value * 2</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
</Types>
"@
Set-Content -Path (Join-Path $moduleDir "$moduleName.format.ps1xml") -Value @"
<Configuration>
<ViewDefinitions>
<View>
<Name>$typeName</Name>
<ViewSelectedBy>
<TypeName>$typeName</TypeName>
</ViewSelectedBy>
<ListControl>
<ListEntries>
<ListEntry>
<ListItems>
<ListItem>
<PropertyName>Value</PropertyName>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
</ListControl>
</View>
</ViewDefinitions>
</Configuration>
"@
Set-Content -Path (Join-Path $moduleDir "$moduleName.psm1") -Value "function Get-TestItem { [pscustomobject]@{ PSTypeName = '$typeName'; Value = 21 } }"

$manifestPath = Join-Path $moduleDir "$moduleName.psd1"
New-ModuleManifest -Path $manifestPath -RootModule "$moduleName.psm1" -FunctionsToExport 'Get-TestItem' `
-TypesToProcess "$moduleName.types.ps1xml" -FormatsToProcess "$moduleName.format.ps1xml"

# Imports the module and reports whether its type and format data are available in the current runspace.
# The 'First' instance waits until the 'Second' instance is running (so its runspace has already been opened)
# before importing the module; the 'Second' instance imports the module only after the 'First' one has.
$script = {
param ($Role, $ManifestPath, $TypeName, $SecondRunspaceOpened, $FirstImportDone)

if ($Role -eq 'First') {
if (-not $SecondRunspaceOpened.Wait(60000)) { throw 'Timed out waiting for the second runspace.' }
}
else {
$SecondRunspaceOpened.Set()
if (-not $FirstImportDone.Wait(60000)) { throw 'Timed out waiting for the first import.' }
}

try {
Import-Module $ManifestPath -ErrorAction Stop
}
finally {
if ($Role -eq 'First') { $FirstImportDone.Set() }
}

[pscustomobject]@{
Role = $Role
RunspaceId = [runspace]::DefaultRunspace.InstanceId
Doubled = (Get-TestItem).Doubled
HasTypeData = $null -ne (Get-TypeData -TypeName $TypeName)
HasFormatData = $null -ne (Get-FormatData -TypeName $TypeName)
}
}
}

It "Loads the data in a runspace that was opened before another runspace in the pool imported the module" {
$secondRunspaceOpened = [System.Threading.ManualResetEventSlim]::new($false)
$firstImportDone = [System.Threading.ManualResetEventSlim]::new($false)
$pool = [runspacefactory]::CreateRunspacePool(1, 2)
$invocations = @()

try {
$pool.Open()

$invocations = foreach ($role in 'First', 'Second') {
$ps = [powershell]::Create()
$ps.RunspacePool = $pool
$null = $ps.AddScript($script).
AddArgument($role).
AddArgument($manifestPath).
AddArgument($typeName).
AddArgument($secondRunspaceOpened).
AddArgument($firstImportDone)
[pscustomobject]@{ PowerShell = $ps; AsyncResult = $ps.BeginInvoke() }
}

$results = foreach ($invocation in $invocations) {
$invocation.PowerShell.EndInvoke($invocation.AsyncResult)
}
}
finally {
$invocations | ForEach-Object { $_.PowerShell.Dispose() }
$pool.Dispose()
$secondRunspaceOpened.Dispose()
$firstImportDone.Dispose()
}

$results | Should -HaveCount 2
$results[0].RunspaceId | Should -Not -Be $results[1].RunspaceId

foreach ($result in $results) {
$result.Doubled | Should -Be 42 -Because "the type data should be loaded in the '$($result.Role)' runspace"
$result.HasTypeData | Should -BeTrue -Because "the type data should be loaded in the '$($result.Role)' runspace"
$result.HasFormatData | Should -BeTrue -Because "the format data should be loaded in the '$($result.Role)' runspace"
}
}
}
Loading