Skip to content

Instantly share code, notes, and snippets.

@alx9r
Created June 23, 2018 14:19
Show Gist options
  • Save alx9r/3be86f9913d1198a5d8d534ecde318d4 to your computer and use it in GitHub Desktop.
Save alx9r/3be86f9913d1198a5d8d534ecde318d4 to your computer and use it in GitHub Desktop.
Demonstration of performance impact of sharing AuthorizationManager when opening runspaces in parallel.
# All this C# is to synthesize a signal that corresponds to when a runspace is
# ready to be used.
# see also PowerShell/PowerShell#7034
Add-Type '
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Linq;
using System;
namespace ns
{
public class RunspaceStateTransition : IEquatable<RunspaceStateTransition>
{
public Nullable<RunspaceAvailability> Availability { get; private set; }
public Nullable<RunspaceState> State { get; private set; }
public RunspaceStateTransition
(
Nullable<RunspaceAvailability> availability,
Nullable<RunspaceState> state
)
{
Availability = availability;
State = state;
}
public bool Equals(RunspaceStateTransition other)
{
if (other==null) return false;
return Availability == other.Availability &&
State == other.State;
}
}
public static class OpenRunspaceWaiterTask
{
public static Task Create(Runspace runspace)
{
var tcs = new TaskCompletionSource<object>();
System.EventHandler<RunspaceAvailabilityEventArgs> availabilityHandler = null;
System.EventHandler<RunspaceStateEventArgs> stateHandler = null;
var log = new ConcurrentQueue<RunspaceStateTransition>();
availabilityHandler = (s,e) => {
log.Enqueue( new RunspaceStateTransition(e.RunspaceAvailability,null));
if ( RunspaceIsOpen(runspace.InitialSessionState,log.ToArray()) )
{
runspace.AvailabilityChanged -= availabilityHandler;
runspace.StateChanged -= stateHandler;
tcs.SetResult(null);
}
};
stateHandler = (s,e) => {
log.Enqueue( new RunspaceStateTransition(null,e.RunspaceStateInfo.State));
if ( RunspaceIsOpen(runspace.InitialSessionState,log.ToArray()) )
{
runspace.AvailabilityChanged -= availabilityHandler;
runspace.StateChanged -= stateHandler;
tcs.SetResult(null);
}
};
runspace.AvailabilityChanged += availabilityHandler;
runspace.StateChanged += stateHandler;
return tcs.Task;
}
public static bool RunspaceIsOpen(
InitialSessionState initialSessionState,
RunspaceStateTransition[] log
)
{
RunspaceStateTransition[] expected = null;
if ( initialSessionState == null ||
initialSessionState.Modules.Count == 0 )
{
expected = new[] {
new RunspaceStateTransition(null,RunspaceState.Opening),
new RunspaceStateTransition(RunspaceAvailability.Available,null),
new RunspaceStateTransition(null,RunspaceState.Opened )
};
}
else
{
expected = new [] {
new RunspaceStateTransition(null,RunspaceState.Opening),
new RunspaceStateTransition(RunspaceAvailability.Available,null),
new RunspaceStateTransition(null,RunspaceState.Opened ),
new RunspaceStateTransition(RunspaceAvailability.Busy,null),
new RunspaceStateTransition(RunspaceAvailability.Available,null)
};
}
return log.SequenceEqual(expected);
}
}
}
' -ReferencedAssemblies @(
'System.Threading.Tasks'
'System.Management.Automation'
'System.Collections.Concurrent'
'System.Linq'
)
Import-Module 'Pester'
$p = [System.Environment]::ProcessorCount
"ProcessorCount: $p"
# one AuthorizationManager per runspace
$initialSessionState = [initialsessionstate]::CreateDefault()
$initialSessionState.ImportPSModule('Pester')
$runspace = 1..$p |
% {
$thisIss = $initialSessionState.Clone()
$thisIss.AuthorizationManager = [initialsessionstate]::CreateDefault().AuthorizationManager
[runspacefactory]::CreateRunspace($thisIss)
}
$awaiter = $runspace | % { [ns.OpenRunspaceWaiterTask]::Create($_) }
$runspace.OpenAsync()
$t_onePer = Measure-Command { [System.Threading.Tasks.Task]::WaitAll($awaiter) }
[pscustomobject]@{
Name = 'One Per'
'Elapsed(ms)' = [int]$t_onePer.TotalMilliseconds
}
# shared AuthorizationManager
$initialSessionState = [initialsessionstate]::CreateDefault()
$initialSessionState.ImportPSModule('Pester')
$runspace = 1..$p |
% {
[runspacefactory]::CreateRunspace($initialSessionState)
}
$awaiter = $runspace | % { [ns.OpenRunspaceWaiterTask]::Create($_) }
$runspace.OpenAsync()
$t_shared = Measure-Command { [System.Threading.Tasks.Task]::WaitAll($awaiter) }
[pscustomobject]@{
Name = 'Shared'
'Elapsed(ms)' = [int]$t_shared.TotalMilliseconds
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment