functions/_waitForExtensionsLock.ps1
|
# <copyright file="_waitForExtensionsLock.ps1" company="Endjin Limited"> # Copyright (c) Endjin Limited. All rights reserved. # </copyright> function _waitForExtensionsLock { [CmdletBinding()] [OutputType([System.Threading.Mutex])] param ( [Parameter(Mandatory)] [string] $ZfPath, [Parameter()] [timespan] $Timeout = [timespan]::FromMinutes(10) ) # Runs that share a '.zf' folder (e.g. parallel builds of the same repository) would otherwise install extensions # into the same folders, and write the same lock file, at the same time. A named mutex works across processes on # every platform. The name is a hash of the full path, as a path can contain characters that a name cannot. $fullPath = [IO.Path]::GetFullPath($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ZfPath)).TrimEnd('/', '\') $pathHash = [System.Security.Cryptography.SHA256]::HashData([System.Text.Encoding]::UTF8.GetBytes($fullPath.ToLowerInvariant())) $mutexName = 'ZeroFailed-{0}' -f [Convert]::ToHexString($pathHash).Substring(0, 16).ToLowerInvariant() $mutex = [System.Threading.Mutex]::new($false, $mutexName) try { # A mutex that another run did not release (e.g. because it was stopped) is acquired by the next run $acquired = $false try { $acquired = $mutex.WaitOne(0) } catch [System.Threading.AbandonedMutexException] { $acquired = $true } if (!$acquired) { Write-Host "Waiting for another run that uses '$fullPath' to finish..." -f Cyan try { $acquired = $mutex.WaitOne($Timeout) } catch [System.Threading.AbandonedMutexException] { $acquired = $true } if (!$acquired) { throw "Timed out after $Timeout waiting for another run that uses '$fullPath' to finish." } } } catch { $mutex.Dispose() throw } return $mutex } |