mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-07-15 16:38:00 +00:00
Defer GUI runspace pool startup
This commit is contained in:
18
functions/private/Close-WinUtilRunspacePool.ps1
Normal file
18
functions/private/Close-WinUtilRunspacePool.ps1
Normal file
@@ -0,0 +1,18 @@
|
||||
function Close-WinUtilRunspacePool {
|
||||
if ($null -eq $sync -or -not $sync.ContainsKey("runspace") -or $null -eq $sync.runspace) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if ($sync.runspace.RunspacePoolStateInfo.State -notin @(
|
||||
[System.Management.Automation.Runspaces.RunspacePoolState]::Closed,
|
||||
[System.Management.Automation.Runspaces.RunspacePoolState]::Closing,
|
||||
[System.Management.Automation.Runspaces.RunspacePoolState]::Broken
|
||||
)) {
|
||||
$sync.runspace.Close()
|
||||
}
|
||||
} finally {
|
||||
$sync.runspace.Dispose()
|
||||
$sync.Remove("runspace")
|
||||
}
|
||||
}
|
||||
39
functions/private/Initialize-WinUtilRunspacePool.ps1
Normal file
39
functions/private/Initialize-WinUtilRunspacePool.ps1
Normal file
@@ -0,0 +1,39 @@
|
||||
function Initialize-WinUtilRunspacePool {
|
||||
if ($sync.runspace -and $sync.runspace.RunspacePoolStateInfo.State -eq [System.Management.Automation.Runspaces.RunspacePoolState]::Opened) {
|
||||
return $sync.runspace
|
||||
}
|
||||
|
||||
if ($sync.runspace) {
|
||||
Close-WinUtilRunspacePool
|
||||
}
|
||||
|
||||
# Set the maximum number of threads for the RunspacePool to the number of threads on the machine.
|
||||
$maxthreads = [Math]::Max([int]$env:NUMBER_OF_PROCESSORS, 1)
|
||||
|
||||
# Create a new session state for parsing variables into our runspace.
|
||||
$hashVars = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync', $sync, $null
|
||||
$offlineVar = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_OFFLINE', $PARAM_OFFLINE, $null
|
||||
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
|
||||
|
||||
$initialSessionState.Variables.Add($hashVars)
|
||||
$initialSessionState.Variables.Add($offlineVar)
|
||||
|
||||
# Get every WinUtil/WPF function and add it to the session state.
|
||||
$functions = Get-ChildItem function:\ | Where-Object { $_.Name -imatch 'winutil|WPF' }
|
||||
foreach ($function in $functions) {
|
||||
$functionDefinition = Get-Content function:\$($function.Name)
|
||||
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $function.Name, $functionDefinition
|
||||
$initialSessionState.Commands.Add($functionEntry)
|
||||
}
|
||||
|
||||
$sync.runspace = [runspacefactory]::CreateRunspacePool(
|
||||
1, # Minimum thread count
|
||||
$maxthreads, # Maximum thread count
|
||||
$initialSessionState, # Initial session state
|
||||
$Host # Machine to create runspaces on
|
||||
)
|
||||
|
||||
$sync.runspace.Open()
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Runspace pool initialized"
|
||||
return $sync.runspace
|
||||
}
|
||||
@@ -67,6 +67,8 @@ public static class WinUtilRunspaceCleanup
|
||||
"@
|
||||
}
|
||||
|
||||
Initialize-WinUtilRunspacePool | Out-Null
|
||||
|
||||
# Create a PowerShell instance
|
||||
$powershell = [powershell]::Create()
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ Describe "Startup performance checkpoints" {
|
||||
It "adds runtime checkpoints for startup hotspots" {
|
||||
$mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw
|
||||
$lazyTabScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTabContent.ps1") -Raw
|
||||
$startupText = "$mainScript`n$lazyTabScript"
|
||||
$runspaceScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1") -Raw
|
||||
$startupText = "$mainScript`n$lazyTabScript`n$runspaceScript"
|
||||
|
||||
foreach ($checkpoint in @(
|
||||
"Runspace pool initialized",
|
||||
|
||||
70
pester/runspace-lifecycle.Tests.ps1
Normal file
70
pester/runspace-lifecycle.Tests.ps1
Normal file
@@ -0,0 +1,70 @@
|
||||
#===========================================================================
|
||||
# Tests - Runspace lifecycle
|
||||
#===========================================================================
|
||||
|
||||
BeforeAll {
|
||||
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
. (Join-Path $script:repoRoot "functions\private\Close-WinUtilRunspacePool.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1")
|
||||
}
|
||||
|
||||
Describe "Initialize-WinUtilRunspacePool" {
|
||||
BeforeEach {
|
||||
$script:sync = [Hashtable]::Synchronized(@{})
|
||||
$script:PARAM_OFFLINE = $false
|
||||
|
||||
function Write-WinUtilPerformanceCheckpoint { param($Name) }
|
||||
Mock Write-WinUtilPerformanceCheckpoint { }
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Close-WinUtilRunspacePool
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name PARAM_OFFLINE -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "creates and reuses one open runspace pool" {
|
||||
$firstPool = Initialize-WinUtilRunspacePool
|
||||
$secondPool = Initialize-WinUtilRunspacePool
|
||||
|
||||
$firstPool.RunspacePoolStateInfo.State | Should -Be ([System.Management.Automation.Runspaces.RunspacePoolState]::Opened)
|
||||
[object]::ReferenceEquals($firstPool, $secondPool) | Should -BeTrue
|
||||
Should -Invoke -CommandName Write-WinUtilPerformanceCheckpoint -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "Runspace pool initialized"
|
||||
}
|
||||
}
|
||||
|
||||
It "closes and removes the active runspace pool" {
|
||||
$pool = Initialize-WinUtilRunspacePool
|
||||
|
||||
Close-WinUtilRunspacePool
|
||||
|
||||
$pool.RunspacePoolStateInfo.State | Should -Be ([System.Management.Automation.Runspaces.RunspacePoolState]::Closed)
|
||||
$script:sync.ContainsKey("runspace") | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Runspace startup wiring" {
|
||||
It "does not create the GUI runspace pool before automation checks" {
|
||||
$mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw
|
||||
$beforePreset = $mainScript.Substring(0, $mainScript.IndexOf('if ($Preset)'))
|
||||
|
||||
$beforePreset | Should -Not -Match '\[runspacefactory\]::CreateRunspacePool'
|
||||
$beforePreset | Should -Not -Match '\$sync\.runspace\.Open\(\)'
|
||||
}
|
||||
|
||||
It "initializes runspaces synchronously for automation paths and after first render for GUI" {
|
||||
$mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw
|
||||
|
||||
$mainScript | Should -Match 'if \(\$Preset\) \{\s+Initialize-WinUtilRunspacePool'
|
||||
$mainScript | Should -Match 'if \(\$Config\) \{\s+Initialize-WinUtilRunspacePool'
|
||||
$mainScript | Should -Match 'Dispatcher\.BeginInvoke\(\[System\.Windows\.Threading\.DispatcherPriority\]::Background, \[action\]\{ Initialize-WinUtilRunspacePool'
|
||||
$mainScript | Should -Match 'Close-WinUtilRunspacePool'
|
||||
}
|
||||
|
||||
It "creates runspaces on demand before queueing background work" {
|
||||
$runspaceScript = Get-Content -Path (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1") -Raw
|
||||
|
||||
$runspaceScript | Should -Match 'Initialize-WinUtilRunspacePool \| Out-Null'
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
BeforeAll {
|
||||
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
. (Join-Path $script:repoRoot "functions\private\Close-WinUtilRunspacePool.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFFeatureInstall.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFAppxRemoval.ps1")
|
||||
@@ -18,6 +20,8 @@ BeforeAll {
|
||||
$initialSessionState.Variables.Add($syncVariable)
|
||||
$script:sync.runspace = [runspacefactory]::CreateRunspacePool(1, 2, $initialSessionState, $Host)
|
||||
$script:sync.runspace.Open()
|
||||
|
||||
function Write-WinUtilPerformanceCheckpoint { param($Name) }
|
||||
}
|
||||
|
||||
function script:Clear-WinUtilRunspaceTestContext {
|
||||
|
||||
@@ -206,6 +206,10 @@ Describe "Compiled WinUtil sanity" {
|
||||
Describe "Runspace sanity" {
|
||||
BeforeAll {
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Close-WinUtilRunspacePool.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1")
|
||||
|
||||
function Write-WinUtilPerformanceCheckpoint { param($Name) }
|
||||
}
|
||||
|
||||
It "returns a single async handle and runs a scriptblock with arguments in the shared runspace pool" {
|
||||
|
||||
@@ -295,6 +295,7 @@ Describe "XAML and sync wiring" {
|
||||
"keys",
|
||||
"ContainsKey",
|
||||
"GetEnumerator",
|
||||
"Remove",
|
||||
"logorender",
|
||||
"checkmarkrender",
|
||||
"warningrender",
|
||||
|
||||
@@ -29,40 +29,6 @@ public enum PackageManagers
|
||||
}
|
||||
"@
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Set the maximum number of threads for the RunspacePool to the number of threads on the machine
|
||||
$maxthreads = [int]$env:NUMBER_OF_PROCESSORS
|
||||
|
||||
# Create a new session state for parsing variables into our runspace
|
||||
$hashVars = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync',$sync,$Null
|
||||
$offlineVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_OFFLINE',$PARAM_OFFLINE,$Null
|
||||
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
|
||||
|
||||
# Add the variable to the session state
|
||||
$InitialSessionState.Variables.Add($hashVars)
|
||||
$InitialSessionState.Variables.Add($offlineVar)
|
||||
|
||||
# Get every private function and add them to the session state
|
||||
$functions = Get-ChildItem function:\ | Where-Object { $_.Name -imatch 'winutil|WPF' }
|
||||
foreach ($function in $functions) {
|
||||
$functionDefinition = Get-Content function:\$($function.name)
|
||||
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $($function.name), $functionDefinition
|
||||
|
||||
$initialSessionState.Commands.Add($functionEntry)
|
||||
}
|
||||
|
||||
# Create the runspace pool
|
||||
$sync.runspace = [runspacefactory]::CreateRunspacePool(
|
||||
1, # Minimum thread count
|
||||
$maxthreads, # Maximum thread count
|
||||
$InitialSessionState, # Initial session state
|
||||
$Host # Machine to create runspaces on
|
||||
)
|
||||
|
||||
# Open the RunspacePool instance
|
||||
$sync.runspace.Open()
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Runspace pool initialized"
|
||||
|
||||
# Create classes for different exceptions
|
||||
|
||||
class WingetFailedInstall : Exception {
|
||||
@@ -97,6 +63,8 @@ Set-Preferences
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Preferences loaded"
|
||||
|
||||
if ($Preset) {
|
||||
Initialize-WinUtilRunspacePool | Out-Null
|
||||
|
||||
# Selects the tweaks from $Preset varible
|
||||
Update-WinUtilSelections -flatJson $sync.configs.preset.$Preset
|
||||
|
||||
@@ -104,21 +72,21 @@ if ($Preset) {
|
||||
Invoke-WinUtilAutoRun
|
||||
|
||||
# Cleanup and exit
|
||||
$sync.runspace.Dispose()
|
||||
$sync.runspace.Close()
|
||||
Close-WinUtilRunspacePool
|
||||
[System.GC]::Collect()
|
||||
Stop-Transcript
|
||||
return
|
||||
}
|
||||
|
||||
if ($Config) {
|
||||
Initialize-WinUtilRunspacePool | Out-Null
|
||||
|
||||
Invoke-WPFImpex -type "import" -Config $Config
|
||||
|
||||
Invoke-WinUtilAutoRun
|
||||
|
||||
# Cleanup and exit
|
||||
$sync.runspace.Dispose()
|
||||
$sync.runspace.Close()
|
||||
Close-WinUtilRunspacePool
|
||||
[System.GC]::Collect()
|
||||
Stop-Transcript
|
||||
return
|
||||
@@ -148,8 +116,7 @@ try {
|
||||
if (-NOT ($readerOperationSuccessful)) {
|
||||
Write-Host "Failed to parse xaml content using Windows.Markup.XamlReader's Load Method." -ForegroundColor Red
|
||||
Write-Host "Quitting WinUtil..." -ForegroundColor Red
|
||||
$sync.runspace.Dispose()
|
||||
$sync.runspace.Close()
|
||||
Close-WinUtilRunspacePool
|
||||
[System.GC]::Collect()
|
||||
exit 1
|
||||
}
|
||||
@@ -255,8 +222,7 @@ $sync["Form"].title = $sync["Form"].title + " " + $sync.version
|
||||
# Set the commands that will run when the form is closed
|
||||
$sync["Form"].Add_Closing({
|
||||
Stop-WinUtilPerformanceTrace -Name "Window closing"
|
||||
$sync.runspace.Dispose()
|
||||
$sync.runspace.Close()
|
||||
Close-WinUtilRunspacePool
|
||||
[System.GC]::Collect()
|
||||
})
|
||||
|
||||
@@ -373,6 +339,7 @@ $sync["Form"].Add_ContentRendered({
|
||||
}
|
||||
|
||||
$sync["Form"].Focus()
|
||||
$sync["Form"].Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Initialize-WinUtilRunspacePool | Out-Null }) | Out-Null
|
||||
})
|
||||
|
||||
# The SearchBarTimer is used to delay the search operation until the user has stopped typing for a short period
|
||||
|
||||
@@ -44,11 +44,11 @@ WinUtil currently does too much work before the first GUI paint. Work from the t
|
||||
|
||||
## P5 - Defer Runspace Pool Startup
|
||||
|
||||
- [ ] Move GUI runspace pool creation after first render when no automation mode is active.
|
||||
- [ ] Keep synchronous runspace pool creation for `-Preset` and `-Config`.
|
||||
- [ ] Prewarm the pool shortly after `ContentRendered` so later actions do not feel delayed.
|
||||
- [ ] Ensure closing the form disposes the pool if it was created.
|
||||
- [ ] Verify buttons that queue work create or reuse the pool safely.
|
||||
- [x] Move GUI runspace pool creation after first render when no automation mode is active.
|
||||
- [x] Keep synchronous runspace pool creation for `-Preset` and `-Config`.
|
||||
- [x] Prewarm the pool shortly after `ContentRendered` so later actions do not feel delayed.
|
||||
- [x] Ensure closing the form disposes the pool if it was created.
|
||||
- [x] Verify buttons that queue work create or reuse the pool safely.
|
||||
|
||||
## P6 - Asset And Theme Cost
|
||||
|
||||
|
||||
Reference in New Issue
Block a user