Repository navigation
ci(unit-tests): bound the apt install steps so a mirror stall cannot burn the job (#974) - #976
Conversation
ab782cd to
1521226
Compare
|
NEEDS CHANGES at It verified the claim I could not. No apt on my host, so this was the open risk. Two corrections to the committed comment:
One correction to the review, in the PR's favour. It disputed a docstring claim Re-derived by me: |
…burn the job (#974) - Give `engine-tests`'s and `pypcap-parity`'s apt steps a `timeout-minutes: 15`. An unbounded stall used to run to the job's own 30-minute budget and report `cancelled`, which correctly fails `Required checks passed` and so blocked two docs-only PRs. - 15 comes from measurement rather than from headroom: across 12 runs the slowest *successful* run of these steps was 508s, so a tighter cap would have failed roughly 4% of healthy legs. - Deliberately do not pass `-o Acquire::Retries=3 -o Acquire::http::Timeout=30` as #974 proposed -- both are already the defaults in the apt noble ships (2.7.14), so they would change no behaviour while reading as new protection. - Fix `pypcap-parity` too, not just the leg that stalled: it installs the union of the same packages from the same mirrors and was equally unbounded. - Add tests/project/test_workflow_apt_timeouts.py, asserting every apt-invoking step in every workflow declares a timeout below its job's budget. No install line and no job logic changed: yaml.safe_load differs only by the two added `timeout-minutes` leaves. The new guard is 6 tests green, and fails on both steps when run against the pre-fix workflow.
1521226 to
0b62fe0
Compare
|
Round 2 GOOD TO GO at I asked whether 646s should move the bound itself, and it said yes — so I have The asymmetry settles it rather than the ratio: too generous costs five extra minutes It offered this as a preference and explicitly did not gate on it. I took it anyway Verified at the new head: The comment records the raise and its reason, so the next person does not read 20 as |
|
GOOD TO GO at It settled the number with its own reasoning rather than deferring: the two real jumps Verified independently at this head rather than taken from my report: One thing it checked that I would have missed: the surviving It also named a data point it declined to use: a fresh run for the force-push was still Not merge-ready yet: CI is 12 ok / 0 fail / 49 inc on this head, restarted by the |
Please follow the guide below
make pylint,make mypy,make isort)make testpasses, and a test case covers the changedocs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Closes #974. An
apt-getstep with no bound ran to the job's own 30-minute budget when a mirror went silent, reportingcancelled— whichRequired checks passedcorrectly refuses to treat as a pass, so an unrelated mirror blocked two docs-only PRs.Changed:
timeout-minutes: 15on the apt step ofengine-testsand ofpypcap-parity, plus a comment recording the failure mode and the measurements. No install line and no job logic touched.Not changed, deliberately: the
-o Acquire::Retries=3 -o Acquire::http::Timeout=30that #974 proposed. Both are already the defaults in the apt noble ships — apt 2.7.14 hasRetries(_config->FindI("Acquire::Retries", 3))(apt-pkg/acquire-item.cc:783) andTimeOut(30)(methods/basehttp.cc:289) read back byConfigFindI("Timeout", TimeOut)(methods/http.cc:413), so passing them changes no behaviour while reading as protection the step had gained. Their already being in force is also why the stall ran to 29m52s rather than failing after 30s and three retries.Why 15 and not a tight "fail fast" number: measured over 12 runs (230 non-zero observations), the slowest successful run of these steps was 508s (
PyShark), p95 338s;pypcap-parityreached 405s. A 5-minute cap would have failed ~4% of healthy legs — a worse defect than the one being fixed. Retry-with-timeoutwrappers are unsafe for the same reason: a legitimate 508s run is indistinguishable from a stall inside any budget short enough to be worth retrying.This PR's own CI confirms it: with the bound in place,
pypcap-parityPython 3.10's apt step took 448s and Python 3.11's 288s, both green, no cancelled legs. A 5-minute cap would have failed the first outright and come within 12 seconds of failing the second.YAML structure comparison:
yaml.safe_loadflattened to leaf paths, base vs head — 177 → 179 leaves, differing only byjobs.engine-tests.steps[2].timeout-minutes = 15andjobs.pypcap-parity.steps[2].timeout-minutes = 15. Bothrun:bodies are byte-identical to before, andbash -nis clean on each.Other
apt-getsites:.github/workflows/has exactly these two, both fixed.examples/benchmark/Dockerfile:76also uses apt but no workflow builds it — it has no Actions job budget to burn and no required check to block, so it is out of scope. Nobrew/choco/snap/yumanywhere.Tests: new
tests/project/test_workflow_apt_timeouts.pyasserts every apt-invoking step in every workflow declares atimeout-minutesbelow its job's budget — so a third unbounded step cannot land. 6 passed (pytest:6 passed, 4 subtests; plainunittest:Ran 6 tests ... OK); against the pre-fix workflow it fails on both steps.tests/test_tier_guard.py109 passed / 596 subtests, and_dependency_gates.pytest_jobs()returns byte-identicalJobtuples before and after, so the text-based workflow guards are unaffected.