Remove-Item can report success while NTFS is still asynchronously finishing a large recursive delete, so Expand-Archive -Force could see leftover entries, queue them for removal itself, and lose the race against our own delete completing - failing with "Cannot find path ... because it does not exist" on the next deploy.master
| @@ -53,23 +53,38 @@ function Wait-AppPoolFullyStopped { | |||||
| # Wipes are attempted a few times with a short pause - file handles (IIS or AV scanning | # Wipes are attempted a few times with a short pause - file handles (IIS or AV scanning | ||||
| # the just-stopped worker process's files) can take a moment to release even after the | # the just-stopped worker process's files) can take a moment to release even after the | ||||
| # worker process itself is confirmed gone. | # worker process itself is confirmed gone. | ||||
| # | |||||
| # Remove-Item returning without an error is not proof the directory is actually empty yet - | |||||
| # NTFS can finish a large recursive delete asynchronously, so a Get-ChildItem right after can | |||||
| # still show remnants. If Expand-Archive -Force then runs against those remnants, it queues | |||||
| # them for removal itself and can lose the race against our own delete finishing, failing with | |||||
| # "Cannot find path ... because it does not exist". So after Remove-Item reports success, poll | |||||
| # until the directory is verifiably empty before trusting the wipe is done. | |||||
| function Remove-DirectoryContentsWithRetry { | function Remove-DirectoryContentsWithRetry { | ||||
| param([string]$Path, [int]$MaxAttempts = 5, [int]$DelayMs = 2000) | |||||
| param([string]$DirPath, [int]$MaxAttempts = 5, [int]$DelayMs = 2000) | |||||
| for($attempt = 1; $attempt -le $MaxAttempts; $attempt++){ | for($attempt = 1; $attempt -le $MaxAttempts; $attempt++){ | ||||
| $failure = $null | |||||
| $problem = $null | |||||
| try { | try { | ||||
| Remove-Item -Recurse -Force $Path -ErrorAction Stop | |||||
| return | |||||
| Remove-Item -Recurse -Force (Join-Path $DirPath '*') -ErrorAction Stop | |||||
| } catch { | } catch { | ||||
| $failure = $_ | |||||
| if($attempt -lt $MaxAttempts){ | |||||
| Write-Host "Wipe attempt $attempt failed (files still locked?) - retrying in $($DelayMs)ms" | |||||
| Start-Sleep -Milliseconds $DelayMs | |||||
| $problem = "delete failed: $($_.Exception.Message)" | |||||
| } | |||||
| if(!$problem){ | |||||
| if(!(Get-ChildItem -Force -LiteralPath $DirPath -ErrorAction SilentlyContinue)){ | |||||
| return | |||||
| } | } | ||||
| $problem = 'residual files still present after delete (async NTFS delete still finishing?)' | |||||
| } | |||||
| if($attempt -lt $MaxAttempts){ | |||||
| Write-Host "Wipe attempt $attempt failed ($problem) - retrying in $($DelayMs)ms" | |||||
| Start-Sleep -Milliseconds $DelayMs | |||||
| } else { | |||||
| throw "Wipe of $DirPath did not complete after $MaxAttempts attempts - $problem" | |||||
| } | } | ||||
| } | } | ||||
| throw $failure | |||||
| } | } | ||||
| if(!(Get-Website -Name $SiteName -ErrorAction SilentlyContinue)){ | if(!(Get-Website -Name $SiteName -ErrorAction SilentlyContinue)){ | ||||
| @@ -106,7 +121,7 @@ if(![string]::IsNullOrWhiteSpace($AdminSiteName) -and (Get-Website -Name $AdminS | |||||
| if(Test-Path $RemoteDir){ | if(Test-Path $RemoteDir){ | ||||
| Write-Host "Wiping $RemoteDir" | Write-Host "Wiping $RemoteDir" | ||||
| Remove-DirectoryContentsWithRetry -Path (Join-Path $RemoteDir '*') | |||||
| Remove-DirectoryContentsWithRetry -DirPath $RemoteDir | |||||
| } else { | } else { | ||||
| New-Item -ItemType Directory -Force -Path $RemoteDir | Out-Null | New-Item -ItemType Directory -Force -Path $RemoteDir | Out-Null | ||||
| } | } | ||||
Powered by TurnKey Linux.