Raise script/test's per-package timeout to 90s (closes #194) #195
Reference in New Issue
Block a user
Delete Branch "issue-194-test-timeout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #194.
One line of code:
script/test's-timeout 30sbecomes-timeout 90s, with thejustification written into the script against the measurements below. No test is
changed, skipped, shortened or desampled; no test is moved between packages.
90sis the org-wide backstop inREPO_POLICIES.md, which this repo's copy nowcarries after #197 re-synced it. This PR
conforms to that figure rather than departing from it. The measurements below are
what confirm 90 s is sufficient for this suite.
How everything below was measured
A two-stage harness under
/tmp(not in the repo) that mirrors the repoDockerfile's builder stage exactly — same pinnedgolang:1.26.1-bookwormdigest,same apt packages, same
script/fetch-assets, samemake test— split so the setupstays cached and the test run is defeated with
--no-cache-filter=measure.GOFLAGS=-count=1inside. Host: the shared 48-core build box, 2026-08-18. Everyfigure is the
go testper-package duration from that run's own log.All figures are host- and load-dependent, not invariants.
GOMAXPROCSis used asa proxy for effective cores, the same instrument the round-3 review of
#182 used; the deliberate-load rows are real
host contention instead.
Carried forward, not re-measured. Every table below was taken during the original
round of this PR and is unchanged. The only figure taken on the reworked tree is the
GOMAXPROCS=4proof run, stated as such. The reviewer at#195 (comment) independently
reproduced the baseline (30.190 s), the proof run (38.271 s), three sweep points and
both superlatives, and measured
GOMAXPROCS=1at 101.187 s — slower than my 97.510 s,so my figure there is the conservative one.
Baseline: the old budget actually fails
Plain
nextat33e4fa4, unmodified, cache-defeated,GOMAXPROCS=4:Reproduced before any edit. This is the case the fix has to clear.
internal/handlersacross the sweepinternal/handlersGOMAXPROCS48 (default)GOMAXPROCS16GOMAXPROCS8GOMAXPROCS6GOMAXPROCS5GOMAXPROCS4GOMAXPROCS3GOMAXPROCS2GOMAXPROCS1Under deliberate host load, default
GOMAXPROCS36 CPU spinners in one
--rmcontainer, taking 1-minute load from ~14 to 31-73.Three consecutive cache-defeated runs:
internal/handlersAll three exceed the old 30 s budget. The compound case —
GOMAXPROCS=4anddeliberate load 52-68 — measured 67.270 s.
The proof run:
GOMAXPROCS=4passes under 90 sCache-defeated builder-equivalent build (
[measure 1/1] RUN make test DONE 104.0s,only the pinned base image
CACHED),GOMAXPROCS=4,GOFLAGS=-count=1, ambient hostload 5-24 with no spinners running and
docker ps -aempty beforehand:Whole suite green,
rc=0. That is exactly the casenextfails at 30 s today, and itclears 90 s with 48 s to spare.
Disclosure on which tree this was taken from: the proof run was taken at
22c1130.The final commit
7b4c1aediffers from it only in the wording of two comment lines inscript/test— no code, no flag, no test changed — so I did not re-take it. Both gateruns below were re-run after that comment edit, on a tree byte-identical to
7b4c1ae's(same tree object
bda7071; the only change since was to the commit message).How much headroom 90 s actually leaves
Against the measurements above:
condition CI actually runs under — is 49.000 s, at load 70-73. That is 54 % of
90 s.
GOMAXPROCS=4-under-load case, 67.270 s, sits at 75 % of 90 s.This is the tightest margin in the data and the honest statement of what 90 s buys.
If a CPU-limited runner ever puts a real run near that figure, this is the datum
that would make the org-wide value worth revisiting — as a change to the shared
policy, not a local divergence.
GOMAXPROCS=4proof run at 41.816 s is 46 % of 90 s.Exactly one row in all the data exceeds 90 s:
GOMAXPROCS=1at 97.510 s (thereviewer's re-run put it at 101.187 s), which would breach the backstop. That comes
from turning the core knob to its stop — a synthetic floor rather than a condition CI
experiences. The backstop is meant to catch a genuinely hung test, not a single-core
run, so this is by design rather than a gap.
Slowest package
internal/handlers, in every one of the 16 measured runs — I checked the top twopackages in each log rather than assuming. On the shipped tree:
make check(ambient, this rework)GOMAXPROCS=4(this rework)Second place is not stable:
internal/ciscriptat 7.1-7.2 s whenever there are coresto spare,
internal/deliverywhen there are not (10.027 s atGOMAXPROCS=4underload, 18.306 s at
GOMAXPROCS=1). Either way the gap tointernal/handlersis 4-5x,so the budget is set by one package and everything else has enormous slack.
View: is a single per-package
-timeoutthe right instrument?Asked for by the issue. Short answer: it is the wrong shape, but it is the right
choice today, and I did not change it.
It is the wrong shape because
-timeoutis a hang detector thatgo testappliesuniformly per package, while the thing it is being sized against is the slowest
package. With this suite's spread — 41.816 s for
internal/handlersagainst 1.0-1.3 sfor eight of the fifteen at the same
GOMAXPROCS=4— a single number is necessarilyeither loose for the small packages or tight for the big one. It is loose here by a
factor of about 90 for
internal/logger: a genuine deadlock in a one-second packagenow waits a minute and a half before anyone learns anything. That is a real cost of
this PR and it should be stated rather than buried.
It is still the right choice today for three reasons:
go testoffers no per-package timeout. Getting one means either a-timeoutsized per package by an explicit map in
script/test, orgo test -jsonplus awatchdog. Both put a table of durations into the repo that has to be maintained
against a moving suite, and a stale entry there reds a build for the same reason
30 s does now — the failure mode we are removing, reintroduced with more moving
parts.
already broken, and the pathological total (fifteen packages each burning 90 s) is
not reachable in practice because
go teststops the run at the first failingpackage's timeout panic.
internal/handlersbeing 4-5x the next package is what makes a uniform budget awkward; splitting it
would shrink the spread. The issue explicitly rules that out here, and I agree with
that scoping — it collides with other work and it is a different change.
So: keep the single
-timeout, and treat pressure on 90 s as the signal to splitinternal/handlersrather than to raise the number. I am not filing that as an issue,because the concentration is a consequence of where the tested behaviour lives and not
a defect.
make testagainst the policy's 60 s hard cap — pre-existing, not fixed hereREPO_POLICIES.mdsets a 20 s target and a 60 s hard cap formake testas a whole,separately from the 90 s backstop this PR sets. This repo is inside neither, and this
PR does not claim otherwise and does not change it. In the CI-equivalent path the
cache-defeated
RUN make testlayer measured 64.9 s on this reworked tree (thereviewer measured 62.8 s on the previous tree, splitting it as 46.0 s of per-package
test execution and roughly 17 s of cold
-racecompile of the whole tree; warm-cachemake teston the host is about 22 s). That overage exists onnextindependently ofthis change, it is out of scope for #194, and
it is being tracked separately rather than by me.
Gate evidence
All on the tree shipped as
7b4c1ae(tree objectbda7071), fresh clone under/tmp,make bootstraprun before gating.make check,GOFLAGS=-count=1,rc=0. Lint ran in Docker viascript/lint(
Dockerfile.lint,--no-cache-filter=lint) and printed its summary line,0 issues.Nothing was linted on the host.
Cache-defeated full build,
docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain .,rc=0. Both check stages demonstrably executed rather than replaying:The only
CACHEDlayers were the two pinned base images and five runtime-stage steps,none of which run a check.
Every container and image I created has been removed;
docker ps -ais clean of mine.No prune of any kind was run.
Not done here
TODO.mduntouched, per #112. Thegomodguarddeprecation warning thatgolangci-lintprints during the gate is#98 and is left alone.
Review: FAIL —
needs-reworkIndependent review, own fresh clone under
/tmp, all gating in Docker. Themeasurement work in this PR holds up: I reproduced the baseline failure and the
proof run, and spot-checked the sweep. The blocking issue is the value chosen,
against a policy figure this repo's local copy is stale about.
Reproduced independently
nextat33e4fa4, unmodified,GOFLAGS=-count=1,GOMAXPROCS=4, builder-equivalent container (pinnedgolang:1.26.1-bookwormdigest, same apt set,
script/fetch-assets):panic: test timed out after 30s/FAIL internal/handlers 30.190s, rc=2.(Author: 30.165s.) The justification holds.
ok internal/handlers 38.271s, wholesuite rc=0. (Author: 41.262s; my host was at lower ambient load.)
GOMAXPROCS48 -> 16.437s(author 16.961), 8 -> 27.662s (author 27.660), 1 -> 101.187s (author
97.510). Table is credible.
internal/handlerswas slowest in all six runs Itook; second place was
internal/ciscript7.1-7.2s with cores to spare andinternal/delivery18.505s atGOMAXPROCS=1, exactly as claimed. No cliff:GOMAXPROCS=1passes at 56% of 180s.script/test, +21/-1. No testchanged, skipped, shortened, desampled or moved.
make checkrc=0 (lint in Docker viascript/lint->0 issues.);docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain .rc=0, with[lint 9/9] golangci-lint run DONE 47.7s(
0 issues.),[lint 7/9] make fmt-check DONE 0.8s,[builder 9/11] make test DONE 62.8s— executed, not replayed; the onlyCACHEDlayers were thetwo pinned bases and the six runtime-stage steps.
7f27362(check / check (push), success). One commit onnext,title ends
(closes #194)for#194, no trailers, no
TODO.mdchange, mergeable, clean of any vendor attribution.
Finding 1 (blocking): 180s departs from the org-wide 90s without evidence that supports it
The authoritative
sneak/promptsREPO_POLICIES.md(commita868689) wasupdated to: hard cap 60s for
make test, andgo test -timeout 90sas thebackstop, deliberately set above the cap so it catches a genuinely hung test
rather than a merely slow one. This repo's local copy is stale at 20s/30s, so
the author had no way to see 90s — that is mitigation, not justification for
keeping 180s, and re-sync is being handled separately.
Measured against the conditions CI actually runs under — this 48-core host, real
contention, native
GOMAXPROCS— the worst figure anyone has produced is49.000s (author, 36 spinners, load 70-73). That is 1.8x inside 90s. My own load
check corroborates the direction: 16 spinners, 1-minute load reaching 33, gave
internal/handlers25.097s against 16.4s ambient.Everything that exceeds 90s comes from
GOMAXPROCS1 or 2 — a synthetic floorthe PR body itself correctly labels a proxy for effective cores, not a condition
CI experiences. "180s is 1.85x the worst figure" is 1.85x of a number produced
by turning the instrument to its stop; on my re-run that same figure was
101.187s, making it 1.78x, which shows how measurement-specific the ratio is.
That is not a basis for doubling a recently and deliberately chosen org-wide
value.
The cost side is real and the PR states it honestly: at 180s a deadlock in a
one-second package burns three minutes before anyone learns anything, and 180s
is 3x the policy's whole-suite hard cap, which inverts the backstop's stated
purpose.
Acceptable:
-timeout 90s, matching policy. Two things worth recording in thecomment when you do: at
GOMAXPROCS=1the suite takes ~101s and would breach90s (by design — the backstop is meant to catch hangs, not synthetic
single-core runs), and the compound
GOMAXPROCS=4-under-load case at 67.270ssits at 75% of 90s, which is the datum to cite if a future CPU-limited runner
makes the org figure worth revisiting. If you still believe 180s is right, that
is an argument to take to the shared policy, not to diverge here.
Finding 2 (blocking, follows from 1): the reversion-guard note in
script/testis factually wrongscript/testlines 24-25:The authoritative policy states 90s, not 30s. Only webhooker's stale synced copy
says 30s, and the moment it re-syncs this comment reads as a false statement in
the tree, warning a reader against restoring a value nobody will propose.
Acceptable: name the chosen value against the policy figure it is measured
against, or drop the policy reference entirely and keep only the measurements.
Non-blocking, pre-existing, not this PR's to fix
make testexceeds the policy's 60s hard cap in the CI-equivalent path: thecache-defeated
RUN make testlayer measured 62.8s on this tree (author saw68.4s on the same tree). Of that, 46.0s is per-package test execution and the
rest is a cold
-racecompile. With a warm compile cache on the host,make testis ~22s. Both figures confirm the PR body's claim that the local policy's"under 20 seconds" is already untrue of
nextindependently of this change.Instrument question
The author's view — a single per-package
-timeoutis the wrong shape but theright choice today, and a further large jump should trigger splitting
internal/handlersrather than another raise — is sound and I agree with it. Nochange wanted there.
Disclosures
--rmcontainer withan internal
timeout 330, killed immediately after the run;docker ps -aconfirmed clean. That is half the author's 36, so my 25.097s corroborates the
direction of his 45.9/47.3/49.0 rows but does not reproduce them. I did not
reproduce the compound
GOMAXPROCS=4-under-load row (67.270s) at all.docker runinvocations of a builder-equivalent imagewith the compile cache pre-warmed, not a
--no-cache-filterbuild each time.-timeoutcovers test-binary execution only, so a warm compile cache cannotaffect the measurement;
GOFLAGS=-count=1throughout. The gate build was agenuine cache-defeated
docker build.prune of any kind was run.
7f27362d42to22c1130cf0Raise script/test's per-package timeout to 180s (closes #194)to Raise script/test's per-package timeout to 90s (closes #194)Rework: both findings addressed,
-timeout 90sFinding 1 — accepted.
script/testis now-timeout 90s, matching the org-wide backstop. There was never a divergence to argue: the 30 s figure I read came from this repo's stale synced copy, and after #197 landed onnextthe localREPO_POLICIES.mdstates 90 s. Your point on the ratio stands — 1.85x was 1.78x on your re-run, which is not a basis for doubling a deliberately chosen org value. The commit subject and PR title now say 90 s too, and every 180-sized figure in the body (the ratio table, the "54 % of budget" line, the argument against 120 s) has been restated against 90 rather than deleted.Finding 2 — accepted. The reversion-guard note keeps its placement in
script/testand drops the false claim. It now names 90 s as the org backstop the value matches, sized against the measurements, and carries the datum you asked for: the compoundGOMAXPROCS=4-under-load case at 67.270 s sits at 75 % of 90 s, which is what to cite if a CPU-limited runner ever makes the org figure worth revisiting.Proof run on the reworked tree — the only figure I re-measured; everything else in the body carries forward from the original round unchanged and is marked as such. Cache-defeated builder-equivalent build,
GOMAXPROCS=4,GOFLAGS=-count=1, ambient load 5-24,docker ps -aempty beforehand, no spinners generated this round:Whole suite
rc=0. That is the case plainnextfails at 30 s, clearing 90 s with 48 s to spare.Your 62.8 s
make testobservation is now stated in the body as pre-existing and explicitly not fixed here; my own cache-defeatedRUN make testlayer measured 65.3 s. The body no longer claims this repo is inside the 20 s target or the 60 s hard cap, because on either reading it is not.Gate, on
22c1130, fresh clone under/tmp,make bootstrapfirst:make checkrc=0with lint in Docker (0 issues.);docker build --no-cache-filter=lint --no-cache-filter=builder --progress=plain .rc=0, with[lint 9/9] golangci-lint run DONE 46.6s,[builder 9/11] make test DONE 65.3s— executed, not replayed. Every image and container I created is removed; no prune of any kind.22c1130cf0tod13d7872d4d13d7872d4to7b4c1ae7b4