Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parallelization for faster dependency download #462

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
PowerShellGet & PSDepend by changing the configuration. Also default to
using PowerShellGet v3 which is a compatibility module that is a wrapper
for the equivalent command in PSResourceGet.
- When resolving dependencies using PSResourceGet, it now uses parallelization
to more quickly get the dependencies downloaded.

### Fixed

Expand Down
64 changes: 55 additions & 9 deletions Resolve-Dependency.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
.PARAMETER PSResourceGetVersion
String specifying the module version for PSResourceGet if the `UsePSResourceGet` switch is utilized.

.PARAMETER PSResourceGetThrottleLimit
Specifies the maximum number of concurrent requests to the PowerShell Gallery
when using PSResourceGet.

.NOTES
Load defaults for parameters values from Resolve-Dependency.psd1 if not
provided as parameter.
Expand Down Expand Up @@ -137,6 +141,10 @@ param
[System.String]
$PSResourceGetVersion,

[Parameter()]
[System.UInt16]
$PSResourceGetThrottleLimit,

[Parameter()]
[System.Management.Automation.SwitchParameter]
$UsePowerShellGetCompatibilityModule,
Expand Down Expand Up @@ -937,16 +945,22 @@ try

$percentagePerModule = [System.Math]::Floor(100 / $modulesToSave.Length)

$progressPercentage = 0
# TODO: This is not necessary unless -Parallell can be used (which isn't the case in older PowerShell versions).
# Inspired from https://stackoverflow.com/questions/67114770/are-non-concurrent-collections-safe-inside-concurrent-collections
$syncProgress = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()

Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies'
# The variable $progressPercent will not be the same one as inside the parallell foreach loop.
$progressPercent = $syncProgress.GetOrAdd('ProgressPercentage', { param($key) return 0 })

foreach ($currentModule in $modulesToSave)
{
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name)
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercent -CurrentOperation 'Restoring Build Dependencies'

# This scriptblock is used by ForEach-Object below.
# TODO: The scriptblock cannot use the $using: scope to access variables when passes to parameter -Process
$forEachObjectScriptBlock = {
$currentModule = $_

$savePSResourceParameters = @{
Path = $PSDependTarget
Path = $using:PSDependTarget
TrustRepository = $true
Confirm = $false
}
Expand All @@ -957,9 +971,11 @@ try
# Modules that Sampler depend on that cannot be refreshed without a new session.
$skipModule = @('PowerShell-Yaml')

$savedModule = $false

if ($savePSResourceParameters.Name -in $skipModule -and (Get-Module -Name $savePSResourceParameters.Name))
{
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name)
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercent -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name)

Write-Information -MessageData ('Skipping the module {0} since it cannot be refresh while loaded into the session. To refresh the module open a new session and resolve dependencies again.' -f $savePSResourceParameters.Name) -InformationAction 'Continue'
}
Expand All @@ -974,12 +990,38 @@ try
{
Write-Warning -Message 'Save-PSResource could not save (replace) one or more dependencies. This can be due to the module is loaded into the session (and referencing assemblies). Close the current session and open a new session and try again.'
}
else
{
$savedModule = $true
}
}

$progressPercentage += $percentagePerModule
$syncProgressCopy = $using:syncProgress

#$syncProgressCopy.progressPercentage += $using:percentagePerModule
$progressPercent = $syncProgressCopy.AddOrUpdate('ProgressPercentage', { param($key) return 0 }, { param($key, $value) return $value + $using:percentagePerModule })

if ($savedModule)
{
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercent -CurrentOperation 'Restoring Build Dependencies' -Status ('Saved module {0}' -f $savePSResourceParameters.Name)
}
}

Write-Progress -Activity 'PSResourceGet:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed
if ($IsCoreCLR)
{
if (-not $PSResourceGetThrottleLimit)
{
$PSResourceGetThrottleLimit = 1
}

$modulesToSave | ForEach-Object -ThrottleLimit $PSResourceGetThrottleLimit -Parallel $forEachObjectScriptBlock
}
else
{
$modulesToSave | ForEach-Object -Process $forEachObjectScriptBlock
}

Write-Progress -Activity 'PSResourceGet:' -PercentComplete 100 -CurrentOperation 'Restoring Build Dependencies' -Completed
}
}
else
Expand All @@ -1006,6 +1048,10 @@ try

Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation 'Bootstrap complete' -Completed
}
catch
{
throw $_
}
finally
{
if ($RegisterGallery)
Expand Down
1 change: 1 addition & 0 deletions Resolve-Dependency.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
#>
UsePSResourceGet = $true
PSResourceGetVersion = '1.0.1'
PSResourceGetThrottleLimit = 2

# PowerShellGet compatibility module only works when using PSResourceGet or ModuleFast.
UsePowerShellGetCompatibilityModule = $true
Expand Down
27 changes: 17 additions & 10 deletions Sampler/Templates/Build/Resolve-Dependency.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -937,16 +937,23 @@ try

$percentagePerModule = [System.Math]::Floor(100 / $modulesToSave.Length)

$progressPercentage = 0
# Inspired from https://stackoverflow.com/questions/67114770/are-non-concurrent-collections-safe-inside-concurrent-collections
$syncProgress = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()

Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies'
# The variable $progressPercent will not be the same one as inside the parallell foreach loop.
$progressPercent = $syncProgress.GetOrAdd('ProgressPercentage', { param($key) return 0 })

foreach ($currentModule in $modulesToSave)
{
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name)
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercent -CurrentOperation 'Restoring Build Dependencies'

$modulesToSave | ForEach-Object -ThrottleLimit 5 -Parallel {
$currentModule = $_

$syncProgressCopy = $using:syncProgress

$progressPercent = $syncProgressCopy.AddOrUpdate('ProgressPercentage', { param($key) return 0 }, { param($key, $value) return $value + $using:percentagePerModule })

$savePSResourceParameters = @{
Path = $PSDependTarget
Path = $using:PSDependTarget
TrustRepository = $true
Confirm = $false
}
Expand All @@ -959,12 +966,14 @@ try

if ($savePSResourceParameters.Name -in $skipModule -and (Get-Module -Name $savePSResourceParameters.Name))
{
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name)
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercent -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name)

Write-Information -MessageData ('Skipping the module {0} since it cannot be refresh while loaded into the session. To refresh the module open a new session and resolve dependencies again.' -f $savePSResourceParameters.Name) -InformationAction 'Continue'
}
else
{
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercent -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name)

# Clear all module from the current session so any new version fetched will be re-imported.
Get-Module -Name $savePSResourceParameters.Name | Remove-Module -Force

Expand All @@ -975,11 +984,9 @@ try
Write-Warning -Message 'Save-PSResource could not save (replace) one or more dependencies. This can be due to the module is loaded into the session (and referencing assemblies). Close the current session and open a new session and try again.'
}
}

$progressPercentage += $percentagePerModule
}

Write-Progress -Activity 'PSResourceGet:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed
Write-Progress -Activity 'PSResourceGet:' -PercentComplete 100 -CurrentOperation 'Restoring Build Dependencies' -Completed
}
}
else
Expand Down
Loading