diff --git a/nupkg/push_packages.ps1 b/nupkg/push_packages.ps1 index 5b37c22572..f192c2a560 100644 --- a/nupkg/push_packages.ps1 +++ b/nupkg/push_packages.ps1 @@ -13,7 +13,9 @@ $failedPackages = @() $totalProjectsCount = $projects.length $nugetUrl = "https://api.nuget.org/v3/index.json" $maxQuotaRetryCount = 3 -$quotaRetryDelaysInSeconds = @(30, 60, 120) +$defaultQuotaRetryDelaysInSeconds = @(30, 60, 120) +$maxPushesPerHour = 250 # NuGet.org rate limit (conservative). Set 0 to disable. +$pushTimestamps = [System.Collections.Generic.List[datetime]]::new() $failedPackagesFilePath = Join-Path $packFolder "failed-packages.txt" Set-Location $packFolder if (Test-Path $failedPackagesFilePath) { Remove-Item $failedPackagesFilePath -Force } @@ -29,6 +31,25 @@ foreach($project in $projects) { if ($nugetPackageExists) { + # Sliding-window rate limiter: ensure we don't exceed $maxPushesPerHour pushes per hour + if ($maxPushesPerHour -gt 0) + { + $windowStart = (Get-Date).AddHours(-1) + $recentPushes = $pushTimestamps | Where-Object { $_ -gt $windowStart } + $pushTimestamps = [System.Collections.Generic.List[datetime]]$recentPushes + + if ($pushTimestamps.Count -ge $maxPushesPerHour) + { + $waitUntil = $pushTimestamps[0].AddHours(1) + $waitSeconds = [int]([math]::Ceiling(($waitUntil - (Get-Date)).TotalSeconds)) + 1 + if ($waitSeconds -gt 0) + { + Write-Warning "Rate limit: $($pushTimestamps.Count) pushes in the last hour (limit: $maxPushesPerHour). Pausing $waitSeconds seconds until the window resets..." + Start-Sleep -Seconds $waitSeconds + } + } + } + $attempt = 0 $pushSucceeded = $false while ($true) @@ -51,7 +72,17 @@ foreach($project in $projects) { break } - $retryDelay = $quotaRetryDelaysInSeconds[$attempt - 1] + # Parse the retry-after value from the NuGet response if present (e.g. "retry after: 2974s") + $retryAfterMatch = ($pushOutput | Out-String) | Select-String -Pattern 'retry after:\s*(\d+)s' -AllMatches + if ($retryAfterMatch -and $retryAfterMatch.Matches.Count -gt 0) + { + $retryDelay = [int]$retryAfterMatch.Matches[0].Groups[1].Value + } + else + { + $retryDelay = $defaultQuotaRetryDelaysInSeconds[$attempt - 1] + } + Write-Warning "NuGet push returned a 4xx response for $nugetPackageName. Retrying in $retryDelay seconds (retry $attempt/$maxQuotaRetryCount)..." Start-Sleep -Seconds $retryDelay } @@ -62,6 +93,10 @@ foreach($project in $projects) { $errorCount += 1 $failedPackages += $nugetPackageName } + else + { + $pushTimestamps.Add((Get-Date)) + } #Write-Host ("Deleting package from local: " + $nugetPackageName) #Remove-Item $nugetPackageName -Force }