Skip to content

Commit 56d2fb6

Browse files
committed
Linux lane: pin the PBS release from the manifest; retry transient GitHub API errors
The first python-315 CI run lost all three stable-minor Linux jobs to HTTP 504s from the GitHub release-listing API (during the Actions outage recovery). Two structural fixes beyond retries: - The CI step now derives PYTHON_DIST_RELEASE from the manifest row's standalone_release_date, so resolve_pbs.py builds the canonical URL with zero API calls (verified: all four pinned assets exist). The newest PBS release stopped carrying our pinned micros once upstream bumped to 3.12.14/3.13.15/3.14.7, so the auto-resolve path had become a mandatory paginated scan on every stable-minor run. Manifest stays the single source of truth; auto-resolution remains the fallback for a missing field, and an explicit PYTHON_DIST_RELEASE still wins. - resolve_pbs.py retries 429/5xx and network errors with backoff, and the fallback scan uses small pages (20/page, 200 releases) — the giant 100-release payloads with hundreds of assets each are exactly what the degraded API times out on.
1 parent d5946a6 commit 56d2fb6

2 files changed

Lines changed: 35 additions & 6 deletions

File tree

.github/workflows/build-python-version.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@ jobs:
191191
# Lets resolve_pbs.py authenticate to the GitHub API and avoid rate limits.
192192
GITHUB_TOKEN: ${{ github.token }}
193193
run: |
194+
# Pin the PBS release from the manifest (single source of truth) so
195+
# resolve_pbs.py needs no API calls at all — the newest PBS release
196+
# no longer carries our pinned micros once upstream bumps them, and
197+
# the release-listing API 504s when degraded (took down this lane).
198+
# An empty/missing manifest field falls back to auto-resolution, and
199+
# a workflow-level PYTHON_DIST_RELEASE override still wins.
200+
if [ -z "${PYTHON_DIST_RELEASE:-}" ]; then
201+
PYTHON_DIST_RELEASE=$(jq -r --arg v "$PYTHON_VERSION_SHORT" \
202+
'.pythons[$v].standalone_release_date // empty' ../manifest.json)
203+
export PYTHON_DIST_RELEASE
204+
echo "PBS release from manifest: ${PYTHON_DIST_RELEASE:-<none, auto-resolve>}"
205+
fi
194206
bash ./package-for-linux.sh x86_64 "_v2"
195207
bash ./package-for-linux.sh aarch64 ""
196208

linux/resolve_pbs.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import json
2222
import os
2323
import sys
24+
import time
2425
import urllib.error
2526
import urllib.request
2627

@@ -31,7 +32,10 @@
3132
# PBS has a very long release history. A target micro that we'd ship is always recent,
3233
# so bound the newest-first fallback scan rather than crawling every page (which both
3334
# burns rate limit and risks gateway timeouts when the asset genuinely doesn't exist).
34-
MAX_FALLBACK_PAGES = 5 # 5 * 100 = 500 most-recent releases
35+
# Small pages on purpose: each PBS release carries hundreds of assets, and big
36+
# release-list payloads are exactly what the GitHub API 504s on when degraded.
37+
PER_PAGE = 20
38+
MAX_FALLBACK_PAGES = 10 # 10 * 20 = 200 most-recent releases
3539

3640

3741
def asset_name(version: str, arch: str, arch_ver: str, release: str) -> str:
@@ -42,15 +46,28 @@ def asset_name(version: str, arch: str, arch_ver: str, release: str) -> str:
4246
)
4347

4448

45-
def _get(url: str) -> bytes:
49+
def _get(url: str, attempts: int = 4) -> bytes:
4650
req = urllib.request.Request(url)
4751
req.add_header("Accept", "application/vnd.github+json")
4852
req.add_header("X-GitHub-Api-Version", "2022-11-28")
4953
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
5054
if token:
5155
req.add_header("Authorization", f"Bearer {token}")
52-
with urllib.request.urlopen(req, timeout=60) as resp:
53-
return resp.read()
56+
for attempt in range(1, attempts + 1):
57+
try:
58+
with urllib.request.urlopen(req, timeout=60) as resp:
59+
return resp.read()
60+
except urllib.error.HTTPError as exc:
61+
# The GitHub API 504s/503s transiently under load (seen taking down
62+
# whole CI runs); retry those with backoff. Other 4xx are real
63+
# answers the callers handle — surface them immediately.
64+
if exc.code not in (429, 500, 502, 503, 504) or attempt == attempts:
65+
raise
66+
except (urllib.error.URLError, TimeoutError):
67+
if attempt == attempts:
68+
raise
69+
time.sleep(2**attempt)
70+
raise AssertionError("unreachable")
5471

5572

5673
def _find_in_release(release: dict, version: str, arch: str, arch_ver: str) -> str | None:
@@ -74,7 +91,7 @@ def resolve(version: str, arch: str, arch_ver: str) -> str:
7491

7592
# Fall back to paging newest-first (bounded) until we find a release with the asset.
7693
for page in range(1, MAX_FALLBACK_PAGES + 1):
77-
releases = json.loads(_get(f"{API}?per_page=100&page={page}"))
94+
releases = json.loads(_get(f"{API}?per_page={PER_PAGE}&page={page}"))
7895
if not releases:
7996
break
8097
for release in releases: # API returns newest-first
@@ -85,7 +102,7 @@ def resolve(version: str, arch: str, arch_ver: str) -> str:
85102
raise SystemExit(
86103
f"No python-build-standalone asset found for "
87104
f"{asset_name(version, arch, arch_ver, '<release>')} "
88-
f"in the {MAX_FALLBACK_PAGES * 100} most-recent releases."
105+
f"in the {MAX_FALLBACK_PAGES * PER_PAGE} most-recent releases."
89106
)
90107

91108

0 commit comments

Comments
 (0)