Repository navigation
ci: retry the tests job apt install with backoff (#456) - #457
Conversation
[Medium impact] The PR appears safe to merge; no new blocking issue was found. SummaryThe helper now gives each apt attempt a shared 70-second deadline and runs GNU
Reviews (2) · Last reviewed commit: "ci: cap each apt attempt with a wall-clo..." · Reviewed by Greptile |
chayapats
left a comment
There was a problem hiding this comment.
❌ BLOCKER — งบ retry ไม่จบใน timeout ของ job สำหรับอาการ DNS ที่ #456 เปิดมา
สัญญาที่บรรทัด 2–4 ของไฟล์นี้ และคอมเมนต์ที่ checks.yml:57 คือ outage สั้น ๆ แบบ "Temporary failure resolving archive.ubuntu.com" จะไม่ทำให้ shard ล้มก่อนเทสเริ่ม ลูป retry เองถูก: 4 ครั้ง, sleep 10/30/60 วินาที, install พังแล้วเริ่มใหม่จาก update, ลิสต์ว่างถูกปฏิเสธ, หมดครั้งแล้วยัง fail ดัง ไม่ข้ามแพ็กเกจ เทส fake apt ครอบคลุมเส้นนี้ และ happy path ผ่านจริง
แต่ apt ที่เจอ DNS ตายไม่ได้คืนเร็ว ลูปด้านนอกจึงไปไม่ถึงในงบของ job และ deterministic ต้องผ่านทุก shard
หลักฐานจาก run 43044244466 attempt 7 (คำสั่งเดิม คำสั่งเดียวกันที่ helper มาห่อ):
- x64 job 130661520282: apt เริ่ม 16:39:51Z จบ exit 100 ที่ 16:47:56Z = 8 นาที 4 วินาที สำหรับ update+install หนึ่งรอบ
- arm64 job 130661520011: apt เริ่ม 16:39:54Z จบ exit 100 ที่ 16:54:09Z = 14 นาที 15 วินาที
- ทั้งสองรอบ update จบด้วยแค่ W: แล้ว exit 0 (เชน
&&เดินต่อ จึงไม่ใช่ update ที่คืน non-zero) จากนั้น install เดินทุกไฟล์ โดย resolver ค้างประมาณ 20 วินาทีต่อ URI จน exit 100 นี่คือเส้นที่ลูปตั้งใจ retry และเทส "warning-only update" จำลองไว้ แต่ fake คืนทันที
checks.yml:40 ตั้ง timeout-minutes: 15 ของทั้ง job ไม่ใช่แค่ apt หลัง apt ยังต้องรัน bun test shard ที่เขียวบน head นี้ใช้ 2 นาที 42 วินาที ถึง 5 นาที 44 วินาที ทั้ง job
- arm64 เริ่ม 16:39:49Z บวก 15 นาทีคือประมาณ 16:54:49Z เหลือประมาณ 40 วินาทีหลัง exit 100 sleep ก้อนแรก 10 วินาที แล้ว attempt 2 เริ่มทั้งที่เหลือประมาณ 30 วินาที ไม่พอให้ติดตั้งซ้ำแล้วรัน shard
- x64 เหลือประมาณ 6 นาที 40 วินาที ถ้ารอบสองสำเร็จในประมาณ 10 วินาที (happy path ของ job 130709169938 ใช้ 18:03:31Z–18:03:41Z) แล้วยังมีที่ให้เทส แต่รอบสองที่ DNS ยังตายจะเดินอีกประมาณ 8 นาที แล้วโดน timeout
- ดังนั้น workflow ยังแดงเมื่อเจออาการเดิม แม้ลูปจะถูกต้องบนกระดาษ
APT_OPTIONS บรรทัด 14 ทำให้ทิศทางตรงข้ามกับคอมเมนต์บรรทัด 13:
Acquire::Retries=5ให้ apt ลองไฟล์ที่พังซ้ำ 5 ครั้ง (apt.conf: "If this is non-zero APT will retry failed files the given number of times") อาการนี้เป็น walk ยาวอยู่แล้ว ตัวเลือกนี้ยืด attempt ไม่ได้ตัด ผมยังไม่ได้วัดตัวคูณจริง จึงไม่ปักว่า attempt 1 จะทะลุ 15 นาทีเสมอ แต่ไม่มี deadline ที่จะกันไว้Acquire::http::Timeout=30ไม่ครอบ URL ที่ฆ่า x64 ซึ่งเป็นhttps://security.ubuntu.comและhttps://archive.ubuntu.com(Acquire::https::Timeoutยังเป็นค่า default 120 วินาที) และรอบนี้ resolver ค้างประมาณ 20 วินาทีต่อ URI ก่อน HTTP timeout จะทำงาน timeout ต่อ URI จึงไม่ครอบ attempt- spawn ที่บรรทัด 38 ไม่มี wall-clock kill ลูก apt-get ที่ค้างกินงบจน job ตาย ข้อความ
apt_install_failedจึงไม่ถูกพิมพ์
ข้ออ้างใน PR ว่า "about 100 s of waiting at most, well inside the 15-minute job timeout" นับแค่ sleep ไม่นับเวลา apt เทสใน tests/ci-apt-install.test.ts ใช้ fake ที่คืนทันที จึงเขียวทั้งที่งบจริงไม่พอ
ทิศทางแก้: ใส่ deadline ทั้งก้อนต่อ attempt แล้ว kill ลูก (happy path ประมาณ 10 วินาที ดังนั้น 60–90 วินาทีต่อคำสั่งยังมีมาร์จิน) ให้ worst case ของ 4 ครั้งบวก sleep 100 วินาทีบวกเวลาเทสของ shard ต่ำกว่า 15 นาที เอา Acquire::Retries=5 ออกหรือตั้ง 0/1 เพราะลูปด้านนอกคือ retry ถ้าจะคง timeout ของ apt ให้ตั้งทั้ง http และ https แต่ห้ามใช้แทน wall-clock kill อย่าแก้ด้วยการขึ้น timeout-minutes อย่างเดียว จะเผา runner นานขึ้นตอน DNS ตายทั้งช่วง เพิ่มเทสที่เช็คว่างบนั้นต่ำกว่า timeout-minutes ใน checks.yml
สิ่งที่ตรวจแล้วไม่ใช่ defect:
- ข้าม mirror ของ ubuntu.com สมเหตุสมผล log x64 มี
mirror+file:/etc/apt/apt-mirrors.txtลอง azure.archive.ubuntu.com, archive.ubuntu.com และ security.ubuntu.com แล้วทุกชื่อ resolve ไม่ได้ checkout กับbun installสำเร็จ ดังนั้น DNS ไม่ได้ตายทั้งเครื่อง mirror นอก ubuntu.com หรือ cache .deb ยังช่วย outage หลายชั่วโมงได้ แต่ #456 ระบุว่าเป็น optional และ PR เลื่อนไว้อย่างตรงไปตรงมา - ข้าม
--error-on=anyสมเหตุสมผล รอบที่พัง update เป็นแค่ W: แล้ว exit 0 จากนั้น install exit 100 ลูป retry เส้นนี้แล้ว--error-on=anyจะทำให้ source อื่นที่แค่ warning พัง job ทั้งที่แพ็กเกจติดตั้งได้ - ชุดแพ็กเกจ, sharding และ matrix ไม่เปลี่ยน
- CI ของ head 1364022 เขียวทั้ง typecheck, 12 shard และ deterministic (run 43075230512) ไม่ได้รัน bun test เองเพราะเครื่องรีวิวไม่มี bun
ก่อน merge นอกจากนี้: Greptile P2 เรื่อง TSDoc ถูกใส่ที่บรรทัด 16–22 แล้ว แต่ thread ยังไม่ resolved และ Greptile รีวิวแค่ c21dc96 ไม่ใช่ 1364022 ไม่ใช่ defect ของโค้ด แต่กฎ merge ยังต้องรอ Greptile บน head นี้
|
⚖️ คำตัดสิน (Tria): เข้าข้าง Duo, BLOCKER ยังอยู่ ยังไม่ merge (head 1364022) เหตุผล
ทำต่อ: Mono แก้ใน PR นี้ แล้วให้ Duo re-review
Merge ยังเป็นการตัดสินของ Champ: CI ต้องเขียวทุกตัวบน head ใหม่ Greptile ต้องรีวิว head ใหม่และไม่มี comment ค้าง (thread TSDoc ยัง unresolved) และ review comment ทุกอันต้องได้รับการตอบ ความมั่นใจ: สูง (ดูเวลาจาก Actions API เองแล้ว และดูโค้ดที่ head 1364022 แล้วว่าไม่มี kill) |
Each attempt (update + install) now runs under sudo timeout --kill-after=5, sharing a 70 s budget, so a resolver-bound apt walk cannot fill the 15-minute job. Drop Acquire::Retries (outer loop is the retry), add the https fetch timeout, and test the worst case against checks.yml timeout-minutes.
|
Fixed in 639feab per Tria's ruling (#457 (comment)), upholding @duo's BLOCKER:
Unchanged: the retry loop, restarting from update, failing loudly, not skipping any packages, no extra mirror, and no |
chayapats
left a comment
There was a problem hiding this comment.
Re-review of head 639feab. Previous BLOCKER is fixed. No new findings.
Verdict
The wall-clock cap holds. A resolver-bound apt walk can no longer fill the 15-minute job, and the worst case still leaves room for the slowest shard. Not an approval; merge stays Champ's call. Greptile has not reviewed this head.
Previous BLOCKER
Fixed, at ops/ci/apt-install.ts.
- Each attempt shares one 70s deadline. The command is
sudo timeout --kill-after=5 <seconds> apt-get …(aptCommand), so the signal sender istimeout, notsudo. Exit 124 and 137 are failed attempts and retry fromupdate. Ifupdatespends the budget,installis not started (left() > 0, else exit 124). Acquire::Retries=0. BothAcquire::http::Timeout=30andAcquire::https::Timeout=30are set. They are not the cap.worstCaseMs()is4 × (70s + 5s kill grace + 2s rounding) + 100s = 408s(6m48s), under the ~7 min bound from the ruling.timeout-minutesis still 15.
Budget check against this head, not only the formula. Run 43078102307 is green (typecheck, 12 shards, deterministic). Slowest job is tests (arm64, 2) at 364s, of which apt was 20s, so non-apt is 344s. Replacing that 20s with the 408s worst case gives 752s (12m32s), 148s under 900s. Slowest apt step on this head was 20s (arm64); x64 was 8–10s. A 70s deadline does not clip a healthy install.
Rounding: Math.ceil on a fresh 70_000ms deadline is 70, not 71. Only the command that actually times out pays the 5s --kill-after, because a command that returns 0 has already exited. One attempt therefore tops out near 75s, inside the 77s the formula allows. timeout 0 disables the timeout (confirmed on GNU coreutils 9.1); the install path refuses a non-positive left(), and update is only started against a fresh 70s deadline, so that footgun is not reachable here.
Kill evidence:
man timeout: default mode (no--foreground) times out children of COMMAND;--kill-aftersends KILL; duration 0 is the only disable.- CI ran the real kill test on both arches:
(pass) the timeout wrapper kills a child that ignores SIGTERMin 6002ms on arm64 shard 5 and 6003ms on x64 shard 5. That is 1s +KILL_AFTER_S, and the assertion requires exit 137. - The apt step log on arm64 shard 4 shows
apt-get update/installrunning through this helper (fetched 15.1 MB, then installed ripgrep, python3-pil, poppler-utils). A missingtimeoutundersudowould have failed the step.
Unchanged, as ruled: retry loop, restart from update, loud failure, no package skipping, no extra mirror, no --error-on=any.
Checked, not findings
- Known limit (kill during dpkg unpack) matches the code. The #456 failure was a resolver stall during download, which this deadline kills before unpack. A later attempt still fails loudly. Not required to auto-repair.
- The +6 min assertion is a fixed allowance, not a measurement. Observed non-apt max is 344s, still under 360s. Margin to the job timeout is 2m28s even if that shard also hits the apt worst case.
tsconfig.jsondoes not listops/ci/**, buttests/ci-apt-install.test.tsimports the module and typecheck passed, so the file was typechecked.- Greptile's P2 thread on the old line is outdated and still unresolved; it was replied to at 18:10Z. Not a code defect. Greptile's only review is on c21dc96, not on 639feab. Champ's merge rule still wants Greptile on this head before merge.
Local GNU timeout was checked here (coreutils 9.1). bun and sudo are not on the review machine; the runner evidence above covers both.
|
⚖️ Tria ruling — re-review at 639feab: ✅ PASS (nothing to fix in code) I agree with Duo's re-review (#pullrequestreview-5863728232). The BLOCKER from my earlier ruling (#issuecomment-6803950205) is fixed. Reasons
Next
Confidence: high |
|
@greptileai please re-review head 639feab (wall-clock deadline + kill per attempt, Acquire::Retries=0, budget test; TSDoc from the earlier P2 is in place). |
Closes #456.
Problem
Every
testsshard ran a baresudo apt-get update && sudo apt-get install ...with no retry. On 2026-11-11, runner DNS failures ("Temporary failure resolving archive.ubuntu.com / security.ubuntu.com") made attempts 1–7 of run 43044244466 (PR #455) fail at that step beforebun teststarted.Change (CI-only)
ops/ci/apt-install.ts: runssudo apt-get updatetheninstall -y --no-install-recommends <packages>, up to 4 attempts with 10 s / 30 s / 60 s backoff.sudo timeout --kill-after=5 <s> apt-get ..., so SIGTERM/SIGKILL reach apt-get itself, not only sudo. Exit 124/137 counts as a failed attempt and is retried. If update spends the whole budget, install is skipped for that attempt (exit 124).timeout-minutes: 15; a test reads timeout-minutes from checks.yml and asserts worst case + 6 min <= it. A healthy install takes about 10 s.Acquire::Retries=0(the outer loop is the retry),Acquire::http::Timeout=30andAcquire::https::Timeout=30. These only help stalled connections; the deadline is the real cap.installfails, the next attempt starts again fromupdate. This matters becauseapt-get updatecan exit 0 when it only warns about unreachable mirrors.apt_install_failed after 4 attempts (exit N). It never skips packages. An empty package list is refused.checks.yml: the step is nowbun ops/ci/apt-install.ts ripgrep python3-pil poppler-utils. The packages, test selection, sharding and matrix are unchanged. Bun is already installed before this step.Not done (scope)
*.ubuntu.comname failed to resolve (azure.archive, archive, security, ports), so switching to another Ubuntu mirror would not have helped. A multi-hour outage will still fail this step, by design. Retries cover short blips.apt-get update --error-on=any, because the runner image also has third-party sources whose warnings would then fail the job. Retrying on install failure covers the same case.Known limit
Tests
bun test tests/ci-apt-install.test.ts tests/ci-shards.test.ts: 17 pass, 0 fail. Covers:updatefailures, with the backoff sequenceupdateafter aninstallfailure, and after an install killed by the deadline (exit 124 and 137)timeoutkills a child that ignores SIGTERM (exit 137 within 1 + 5 s)aptCommandputstimeoutundersudo, right in front ofapt-gettimeout-minutesbun run typecheck: clean.