Skip to content

Commit a3ce016

Browse files
authored
[eng/common] Make helper function "Install-ModuleIfNotInstalled" threadsafe (#6521)
- Fixes #6514
1 parent 639c95d commit a3ce016

File tree

2 files changed

+76
-26
lines changed

2 files changed

+76
-26
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Manual test to verify the threadsafety of "Install-ModuleIfNotInstalled"
2+
# If test runs several iterations with no errors, the function is likely threadsafe
3+
# If test throws any errors related to installing or loading the module, the function likely has a race condition
4+
5+
$command = {
6+
. $PWD/../../common/scripts/Helpers/PSModule-Helpers.ps1
7+
Write-Host 'Install-ModuleIfNotInstalled "powershell-yaml" "0.4.1" | Import-Module'
8+
Install-ModuleIfNotInstalled "powershell-yaml" "0.4.1" | Import-Module
9+
Write-Host
10+
}
11+
12+
while ($true) {
13+
Write-Host 'Uninstall-Module "powershell-yaml"'
14+
Uninstall-Module "powershell-yaml"
15+
16+
$job1 = Start-Job -ScriptBlock $command
17+
$job2 = Start-Job -ScriptBlock $command
18+
$job3 = Start-Job -ScriptBlock $command
19+
Wait-Job -Job $job1, $job2, $job3 | Out-Null
20+
Receive-Job -Job $job1, $job2, $job3
21+
22+
Write-Host
23+
24+
$job1 = Start-Job -ScriptBlock $command
25+
$job2 = Start-Job -ScriptBlock $command
26+
$job3 = Start-Job -ScriptBlock $command
27+
Wait-Job -Job $job1, $job2, $job3 | Out-Null
28+
Receive-Job -Job $job1, $job2, $job3
29+
30+
Write-Host
31+
}

eng/common/scripts/Helpers/PSModule-Helpers.ps1

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function Update-PSModulePathForCI()
4747
}
4848
}
4949

50+
# Manual test at eng/common-tests/psmodule-helpers/Install-Module-Parallel.ps1
5051
# If we want to use another default repository other then PSGallery we can update the default parameters
5152
function Install-ModuleIfNotInstalled()
5253
{
@@ -65,35 +66,53 @@ function Install-ModuleIfNotInstalled()
6566

6667
if ($modules.Count -eq 0)
6768
{
68-
$repositories = (Get-PSRepository).Where({ $_.SourceLocation -eq $repositoryUrl })
69-
if ($repositories.Count -eq 0)
70-
{
71-
Register-PSRepository -Name $repositoryUrl -SourceLocation $repositoryUrl -InstallationPolicy Trusted
72-
$repositories = (Get-PSRepository).Where({ $_.SourceLocation -eq $repositoryUrl })
73-
if ($repositories.Count -eq 0) {
74-
Write-Error "Failed to registory package repository $repositoryUrl."
75-
return
69+
# Use double-checked locking to avoid locking when module is already installed
70+
$mutex = New-Object System.Threading.Mutex($false, "Install-ModuleIfNotInstalled")
71+
$null = $mutex.WaitOne()
72+
73+
try {
74+
# Check installed modules again after acquiring lock
75+
$modules = (Get-Module -ListAvailable $moduleName)
76+
if ($version -as [Version]) {
77+
$modules = $modules.Where({ [Version]$_.Version -ge [Version]$version })
7678
}
77-
}
78-
$repository = $repositories[0]
79-
80-
if ($repository.InstallationPolicy -ne "Trusted") {
81-
Set-PSRepository -Name $repository.Name -InstallationPolicy "Trusted"
82-
}
8379

84-
Write-Host "Installing module $moduleName with min version $version from $repositoryUrl"
85-
# Install under CurrentUser scope so that the end up under $CurrentUserModulePath for caching
86-
Install-Module $moduleName -MinimumVersion $version -Repository $repository.Name -Scope CurrentUser -Force
87-
88-
# Ensure module installed
89-
$modules = (Get-Module -ListAvailable $moduleName)
90-
if ($version -as [Version]) {
91-
$modules = $modules.Where({ [Version]$_.Version -ge [Version]$version })
80+
if ($modules.Count -eq 0)
81+
{
82+
$repositories = (Get-PSRepository).Where({ $_.SourceLocation -eq $repositoryUrl })
83+
if ($repositories.Count -eq 0)
84+
{
85+
Register-PSRepository -Name $repositoryUrl -SourceLocation $repositoryUrl -InstallationPolicy Trusted
86+
$repositories = (Get-PSRepository).Where({ $_.SourceLocation -eq $repositoryUrl })
87+
if ($repositories.Count -eq 0) {
88+
Write-Error "Failed to register package repository $repositoryUrl."
89+
return
90+
}
91+
}
92+
$repository = $repositories[0]
93+
94+
if ($repository.InstallationPolicy -ne "Trusted") {
95+
Set-PSRepository -Name $repository.Name -InstallationPolicy "Trusted"
96+
}
97+
98+
Write-Host "Installing module $moduleName with min version $version from $repositoryUrl"
99+
# Install under CurrentUser scope so that the end up under $CurrentUserModulePath for caching
100+
Install-Module $moduleName -MinimumVersion $version -Repository $repository.Name -Scope CurrentUser -Force
101+
102+
# Ensure module installed
103+
$modules = (Get-Module -ListAvailable $moduleName)
104+
if ($version -as [Version]) {
105+
$modules = $modules.Where({ [Version]$_.Version -ge [Version]$version })
106+
}
107+
108+
if ($modules.Count -eq 0) {
109+
Write-Error "Failed to install module $moduleName with version $version"
110+
return
111+
}
112+
}
92113
}
93-
94-
if ($modules.Count -eq 0) {
95-
Write-Error "Failed to install module $moduleName with version $version"
96-
return
114+
finally {
115+
$mutex.ReleaseMutex()
97116
}
98117
}
99118

0 commit comments

Comments
 (0)