Modules/businessdev.ALbuild.Core/Public/New-ALbuildCacheMutex.ps1
|
function New-ALbuildCacheMutex { <# .SYNOPSIS Opens the cross-process lock of an ALbuild cache entry, or returns $null when Windows refuses it. .DESCRIPTION Every cache that builds on one host share (artifacts, first-party symbols, call graph sources, images) is locked with a named mutex whose name comes from Get-ALbuildCacheLockName. Creating that mutex can fail for reasons that have nothing to do with the build: on 2026-11-03 the MCP server on SRV05 got "The system cannot open the device or file specified" (Win32 110) for about 40 minutes, and every build that needed a new artifact failed, although nothing was wrong with the cache. The lock only prevents two builds from doing the same work at once; the caches themselves are published atomically (extract to a staging folder, then rename). So a failed lock must not fail the build. This function retries a few times, and when the mutex still cannot be opened it logs everything needed to diagnose it - folder, mutex name, error, Win32 code, account, session, process - and returns $null. The caller decides what $null means: work without the lock (extraction) or treat the entry as busy (removal). .PARAMETER Path The cache folder (or lock key) the mutex protects, as passed to Get-ALbuildCacheLockName. .PARAMETER RetryCount Further attempts after the first failure. Default 3. .PARAMETER RetryDelaySeconds Base delay between attempts; attempt n waits n times this. Default 2 (2 + 4 + 6 s). .EXAMPLE $mutex = New-ALbuildCacheMutex -Path $targetFolder if (-not $mutex) { Write-ALbuildLog -Level Warning 'Continuing without the cache lock.' } .OUTPUTS System.Threading.Mutex, or $null when it could not be opened. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Opens an in-memory lock handle; nothing on disk changes.')] [CmdletBinding()] [OutputType([System.Threading.Mutex])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Path, [ValidateRange(0, 10)] [int] $RetryCount = 3, [ValidateRange(0, 60)] [int] $RetryDelaySeconds = 2 ) $name = Get-ALbuildCacheLockName -Path $Path $attempt = 0 while ($true) { $attempt++ try { return (New-ALbuildNamedMutex -Name $name) } catch { $e = $_.Exception while ($e -is [System.Management.Automation.MethodInvocationException] -and $e.InnerException) { $e = $e.InnerException } $win32 = $e.HResult -band 0xFFFF if ($attempt -le $RetryCount) { Write-ALbuildLog -Level Verbose "Cache lock '$name' could not be opened (attempt $attempt, Win32 $win32): $($e.Message) Retrying." Start-Sleep -Seconds ($RetryDelaySeconds * $attempt) continue } # Environment, not WindowsIdentity: the module's CI also runs on Linux. $identity = if ([Environment]::UserDomainName) { "$([Environment]::UserDomainName)\$([Environment]::UserName)" } else { [Environment]::UserName } $process = [System.Diagnostics.Process]::GetCurrentProcess() Write-ALbuildLog -Level Warning ("Cache lock not available after $attempt attempt(s): $($e.Message) " + "[folder '$Path', mutex '$name', $($e.GetType().FullName), HResult 0x$('{0:X8}' -f $e.HResult), Win32 $win32, " + "account '$identity', session $($process.SessionId), process $($process.Id)]") return $null } } } |