Skip to content

v2: the abandon test waits for the abandoned attempt to end; the stub census prints every failure's own text - #618

Merged
EricAndrechek merged 2 commits into
v2from
v2-flight-race
Oct 10, 2026
Merged

EricAndrechek merged 2 commits into
v2from
v2-flight-race

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

Summary

On #617, the required leg v1-abi-conformance (go go.mod darwin-arm64) failed. Its log said only FAILED TestRegistryAFetchNoOpenWaitsForIsAbandoned. This PR does two things:

  1. Classifies that failure and fixes the test. It is a scheduling-dependent wait in the test, not a data race. Reproduced on darwin-arm64 at the 2.0.0-dev.7 tree (origin/v2 @ 6247400), with the stubs built by scripts/abi-v1/build-stubs.sh. Every count below is measured:

    run runs failed race reports
    -race -count=500 (default GOMAXPROCS) 500 0 0
    -race -count=500 -cpu 1,2,8: cpu 1 500 36 0
    the same: cpu 2 500 0 0
    the same: cpu 8 500 0 0
    -count=200, no -race 200 0 —

    Every failure printed the same text, at the second waitParked:

    registry_flight_test.go:555: GET /_gate/parked/s-flight-abandon?n=2: 504 Gateway Timeout
    --- FAIL: TestRegistryAFetchNoOpenWaitsForIsAbandoned (60.08s)
    

    The mechanism. The canceled open returns as soon as it leaves the attempt (leave). The attempt's fetch goroutine sees that cancel later, and until it does, the request's fetchFlight is still in r.fetches. The test started the next open as soon as the canceled open returned, so in that window the next open's fetch could join the abandoned fetch. That fetch then has a waiter again, so it correctly goes on. Only one request ever reaches the gate, and the wait for two times out after the server's 60 s.

    A temporary instrumented copy of the original sequence (not committed) recorded, through onFetchWait, which fetch each caller waited on. At -race -cpu 1 it measured 31 of 500 windows. In all 31, the next open had joined the abandoned fetch, and in none of the 469 others had it. In all 500, the next open got 26.8.15.10 with no error and the tag was requested once. The registry's answer is right in the window; only the test's expectation of two requests depends on scheduling.

    The fix (test only). Before the next open, the test now waits on the abandoned attempt's done channel. That channel closes only after the attempt's fetch has been left and, with no waiter left, canceled. The test then checks that the request has neither an attempt nor a fetch in progress. Everything it proved before still holds: the canceled open gets context.Canceled, the next open's request reaches the gate too, the next open succeeds, and the tag is fetched once. After the fix, -race -count=500 passed 500/500 and -race -count=500 -cpu 1,2,8 passed 1500/1500 (cpu 1: 500/500). Planted check: with leave no longer canceling the fetch, the fixed test fails (registry_flight_test.go:567: the abandoned attempt's end: no answer in 30s).

    No binding code changes, so there is no CHANGELOG line. Python, TypeScript and Rust have no equivalent test: their flight tests never wait for a second parked request.

  2. The stub census prints every failure's own text. scripts/abi-v1/stub_census.py read the -json stream, kept each failed test's name and dropped its output. The org keeps no Actions artifacts, so the job log was all there was, and no one could tell a race report from an assertion. Now, under each FAILED <name>, the failure's own text goes in a collapsible ::group::. It is capped at 400 lines (the first 300 and the last 100 kept, the cut said as "truncated"), and every line is prefixed so none can be read as a workflow command:

    • go: the test's own output events, so an assertion message and any WARNING: DATA RACE report appear. These are keyed by package and test, so a same-named test in another package lends none of its output. A package that failed outside any test is now refused by name, with its own output: a race after the tests, a panic in TestMain, or a build failure (go 1.24+ build-output, joined through FailedBuild). Package-level output beside a failing test is printed when it holds more than go's own framing.
    • junit (pytest): the failure's message and text, plus any captured stdout or stderr.
    • vitest: failureMessages.
    • cargo: the thread '<name>' panicked at block (the leg runs --nocapture) and libtest's ---- <name> stdout ---- section.
    • A runner that exits nonzero with no failing test (a crash, a build error): its own output, and it is still refused.

    Every existing refusal is kept. The selftest plants each case. Eleven planted defects in a copy of the census (no group, no prefix, keyed by test only, no cap, no package refusal, no package output, no FailedBuild, no junit text, no vitest messages, no cargo panic, no runner output) each fail the selftest for their own reason. A real go test -race -json stream from go 1.27.2, with a planted race, an assertion, a parse failure and a panic after m.Run, printed all four texts.

    The other legs. The Rust leg had the same blind spot: rust.sh sends cargo's output to a file and prints only the census. It is fixed here, because it is the same code. The Python leg (pytest -q to the console) and the TS leg (--reporter=verbose to the console) already printed failure text from reading their scripts, and now also get it in the census. scripts/check-suite.sh tees the runner's output, so it already had the text. Listed, not changed: scripts/lib/standalone_census.py (the Go no-artifacts leg) quotes only the last 12 lines of a failed test's output and no package-level output, so a race report's head would be cut there.

Test plan

  • go test ./... with CHTYPES_ABI2_STUBS: exit 0
  • go test -race ./chtypes/ ./internal/ocifetch/ with the stubs: exit 0
  • scripts/abi-v1/conformance/go.sh end to end with the stubs: exit 0, census 421 passed, 7 skipped, 0 failed
  • fixed test: -race -count=500 exit 0; -race -count=500 -cpu 1,2,8 exit 0 (1500/1500)
  • scripts/abi-v1/stub_census.py --selftest: exit 0 (also under Python 3.11)
  • check-selftests-wired.py, lint-actions, lint-spelling, lint-cited-paths, policy-merge-check.py --check-ci-names, --check-guide, lint-public.sh: each exit 0

Related issues

Refs #491

🤖 Generated with Claude Code

EricAndrechek and others added 2 commits October 10, 2026 18:12
… census prints every failure's own text

TestRegistryAFetchNoOpenWaitsForIsAbandoned started the next open as soon
as the canceled open returned. The canceled open returns when it leaves the
attempt; the attempt's own goroutine sees the cancel later, and until it
does the fetch is still the request's. An open made in that window joins
the live fetch (correctly: it has a waiter again, so it goes on), only one
request reaches the gate, and waitParked(2) fails at the server's 60 s
bound with a 504. Measured on darwin-arm64 under -race: 36 of 500 at
-cpu 1, none at -cpu 2 or 8, no race report in any run. The test now waits
on the abandoned attempt's done channel, which closes only after its fetch
has been left and canceled, and checks that neither an attempt nor a fetch
of the request is left. It proves what it did before.

scripts/abi-v1/stub_census.py kept a failed test's name and dropped its
text, so the job log said only "FAILED <name>". Every failure now prints its
own text in a ::group:: under its name, capped at 400 lines (head and tail
kept, the cut said), each line prefixed so none is read as a workflow
command: go's per-test output (assertions, DATA RACE reports), a go package
that failed outside any test (refused by name: a race after the tests, a
panic in TestMain, a build failure), junit failure text, vitest
failureMessages, cargo's panic and captured stdout, and a runner that exited
nonzero with no failing test. Selftest cases plant each.

Refs #491

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@EricAndrechek
EricAndrechek merged commit 445a933 into v2 Oct 10, 2026
54 checks passed
@EricAndrechek
EricAndrechek deleted the v2-flight-race branch October 10, 2026 22:33
EricAndrechek added a commit that referenced this pull request Oct 11, 2026
The next dev release: a declined export's payload is absent in all four
bindings, decided from export_declined (#620); the v2 spec states NULL
out_export on a declined export, the pre-init and post-shutdown refusals, and
chs_preview_row's exactly-one-row rule (#621, #622; prose, the fingerprint is
unchanged); the filter enforcement gate lifted again for 11 pairs and a
CREATE-level divergence registered (#617); a test-timing fix and failure text
in the stub census (#618). It pairs with artifact build 20261011.105019 at
fingerprint d60a681e. The same eight files as the 2.0.0-dev.7 bump.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant