Repository navigation
ci: run the whole suite under the race detector, 450s → 123s - #2912
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
55288d8 to
e77e2fd
Compare
8f1fd22 to
ead0920
Compare
c69350f to
b9a4343
Compare
ad20412 to
68e4fc7
Compare
The case walked 11,225,088 epochs of big.Float arithmetic to reach the point where the geometric schedule pays nothing: 24.7 s on an idle machine and about 13 min under the race detector, past the 10m timeout the race lanes pass - the engine_v2 2/3 lane died on it (XinFinOrg#2912). The series converges to the halving supply for any parameter pair - the reward/supply ratio only sets how fast - so the sweep now runs on a scaled-down supply whose whole curve is 3,450 epochs, and asserts the same two properties: the per-epoch reward dies out and what is issued stays below the supply. Mainnet's numbers are checked at the two epochs the walk pinned down (11,225,087 pays 1 wei, 11,225,088 pays nothing), which costs two calls. The -short gate goes away with the cost: the case runs in 10 ms.
e2709a8 to
a5367eb
Compare
The case walked 11,225,088 epochs of big.Float arithmetic to reach the point where the geometric schedule pays nothing: 24.7 s on an idle machine and about 13 min under the race detector, past the 10m timeout the race lanes pass - the engine_v2 2/3 lane died on it (XinFinOrg#2912). The series converges to the halving supply for any parameter pair - the reward/supply ratio only sets how fast - so the sweep now runs on a scaled-down supply whose whole curve is 3,450 epochs, and asserts the same two properties: the per-epoch reward dies out and what is issued stays below the supply. Mainnet's numbers are checked at the two epochs the walk pinned down (11,225,087 pays 1 wei, 11,225,088 pays nothing), which costs two calls. The -short gate goes away with the cost: the case runs in 10 ms.
The case walked 11,225,088 epochs of big.Float arithmetic to reach the point where the geometric schedule pays nothing: 24.7 s on an idle machine and about 13 min under the race detector, past the 10m timeout the race lanes pass - the engine_v2 2/3 lane died on it (XinFinOrg#2912). The series converges to the halving supply for any parameter pair - the reward/supply ratio only sets how fast - so the sweep now runs on a scaled-down supply whose whole curve is 3,450 epochs, and asserts the same two properties: the per-epoch reward dies out and what is issued stays below the supply. Mainnet's numbers are checked at the two epochs the walk pinned down (11,225,087 pays 1 wei, 11,225,088 pays nothing), which costs two calls. The -short gate goes away with the cost: the case runs in 10 ms.
The case walked 11,225,088 epochs of big.Float arithmetic to reach the point where the geometric schedule pays nothing: 24.7 s on an idle machine and about 13 min under the race detector, past the 10m timeout the race lanes pass - the engine_v2 2/3 lane died on it (XinFinOrg#2912). The series converges to the halving supply for any parameter pair - the reward/supply ratio only sets how fast - so the sweep now runs on a scaled-down supply whose whole curve is 3,450 epochs, and asserts the same two properties: the per-epoch reward dies out and what is issued stays below the supply. Mainnet's numbers are checked at the two epochs the walk pinned down (11,225,087 pays 1 wei, 11,225,088 pays nothing), which costs two calls. The -short gate goes away with the cost: the case runs in 10 ms.
0faaea0 to
784c4f4
Compare
a71816c to
e538686
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved CI defects can skip the only test job for code-changing PRs, omit existing example and fuzz seed checks, and cancel pending push CI/deployment runs.
3 open issues
What changed in this PR
This PR consolidates CI testing into a full race-detector suite and rebalances its workload across 13 lanes. It changes CI tooling and documentation without changing node behavior.
Changes:
- Replaces duplicate test jobs with full race testing, split by package or test name.
- Adds shard validation, configurable test filters and timeouts, and a separate static-check job.
- Updates test skip rules, run cancellation, caches, and deployment dependencies.
| File | Description |
|---|---|
Makefile |
Adds full-suite shard targets and partition checks. |
build/shard.sh |
Assigns packages deterministically and validates shard coverage. |
build/ci.go |
Supports test-name filters and configurable timeouts. |
AGENTS.md |
Documents full race testing and matching local commands. |
.github/workflows/ci.yml |
Reorganizes test lanes, static checks, timeouts, and deployment gates. |
Review effort: Balanced
6e2341e to
8d0a349
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Rename detection can incorrectly skip the sole CI test job after source or fixture moves, and the Makefile filter silently omits valid entry points named exactly Example, Fuzz, or Test.
2 open issues
3 resolved since last review
Review effort: Balanced
… job The non-race `tests` job and the detector ran the same tests: of the 130 packages that have test files, both covered all 130. `core/vm/privacy` had no lane in either - its tests are behind -tags=privacytests - and it is unused and scheduled for deletion (XinFinOrg#2895), so the test job asserted nothing the detector does not. The detector becomes the test job: the whole suite, under -race, in thirteen lanes. The bulk of the tree is split by import path (cksum) into four lanes, so a new package lands in one lane instead of shifting every boundary after it, and `make check-shards` asserts that the lanes partition the package list. The assignment and both checks live in `go run build/ci.go shard`, so the split needs no shell script. The static job runs the checks, and reads the lane count back from the workflow: a lane dropped without the total moving fails there instead of silently leaving its packages untested. engine_v2_tests (624s) runs as five lanes sliced by test name and core (308s) as two, both enumerated from the package itself so no table needs maintaining. cmd/XDC is one more: at 79s it is a single package, and no split of the tree can move it. rpc stands alone because its 32 MiB websocket test trips the codec's 30s pong timeout under -p 4. core/vm/privacy keeps no lane at all. gofmt, `go mod tidy -diff` and `go generate` move to a `static` job that runs once per run instead of once per shard, with a commit-keyed go-build cache: a key holding only go.sum froze an entry at the commit that first wrote it, and every lane then paid 50-80s to recompile. The deploy jobs followed `tests`; they now wait for the detector, which runs on dev-upgrade and tags even when the diff holds no Go code - otherwise a documentation merge would skip the detector and, through `needs`, the deployment as well. build/ci.go test forwards -run, which the name slices need.
8d0a349 to
8195949
Compare
Five timer tests here waited four to five seconds per assertion, so `make test`, which runs one package at a time, spent 38s on nothing but sleep in this package: the third slowest in the full run. The durations were picked to be human-observable, but nothing in the tests depends on their absolute size: the only requirement is that a mid-flight Reset lands before the timeout, or the callback fires at the original deadline and the test reports a reset that never happened. At 5000ms/4000ms that margin was one second. At 500ms/200ms it is 300ms, which still clears scheduler jitter under `-race` with a single CPU and every core busy, and every other wait follows from the interval. The timer tests also run in parallel now, so the package costs its longest test rather than their sum, and the two reset tests, which differed only in whether the callback returns an error, became one table test. The assertions no longer compare `time.Now()` against an exact deadline: each callback stamps itself when it runs, and the tests fail only when the callback arrives earlier than a landed reset allows. Scheduler jitter can only delay a callback, so a slow runner cannot turn these into false failures. Every wait is bounded and every callback channel is buffered, so a timer that stops calling back fails the package instead of hanging it, and each test stops its timer on the way out. All four cost under 4s now, the line XinFinOrg#2947 draws for a -short carve-out, so they lose their testshort.Skip calls and start running in -short, which is what `make quick-test` uses. The CI race job has run the full suite since XinFinOrg#2912, where the gate was already a no-op. The package costs 38.0s full and 1.0s -short before, 1.2s both ways after.
Five timer tests here waited four to five seconds per assertion, so `make test`, which runs one package at a time, spent 38s on nothing but sleep in this package: the third slowest in the full run. The durations were picked to be human-observable, but nothing in the tests depends on their absolute size: the only requirement is that a mid-flight Reset lands before the timeout, or the callback fires at the original deadline and the test reports a reset that never happened. The only timing judgment left is on the early side: a callback that saw the mid-flight Reset must arrive after interval plus half of resetAt, which sits 100ms above the 500ms deadline a callback that missed the reset would hit, so scheduler jitter can only push it further away. Every other wait follows from the interval. The timer tests also run in parallel now, so the package costs its longest test rather than their sum, and the two reset tests, which differed only in whether the callback returns an error, became one table test. The assertions no longer compare `time.Now()` against an exact deadline: each callback stamps itself when it runs, and the tests fail only when the callback arrives earlier than a landed reset allows. Scheduler jitter can only delay a callback, and every wait is bounded at 4x the interval with a buffered callback channel, so a loaded runner stays green while a timer that stops calling back fails inside that bound instead of hanging the package. Each test stops its timer on the way out, unless the test has already failed: then the countdown may be the reason, its startTimer may be wedged, and StopTimer would block instead of letting the failure surface. All four cost under 4s now, the line XinFinOrg#2947 draws for a -short carve-out, so they lose their testshort.Skip calls and start running in -short, which is what `make quick-test` uses. The CI race job has run the full suite since XinFinOrg#2912, where the gate was already a no-op. The package costs 38.0s full and 1.0s -short before, 1.2s both ways after.
Five timer tests here waited four to five seconds per assertion, so `make test`, which runs one package at a time, spent 38s on nothing but sleep in this package: the third slowest in the full run. The durations were picked to be human-observable, but nothing in the tests depends on their absolute size: the only requirement is that a mid-flight Reset lands before the timeout, or the callback fires at the original deadline and the test reports a reset that never happened. The only timing judgment left is on the early side: a callback that saw the mid-flight Reset must arrive after interval plus half of resetAt, which sits 100ms above the 500ms deadline a callback that missed the reset would hit, so scheduler jitter can only push it further away. Every other wait follows from the interval. The timer tests also run in parallel now, so the package costs its longest test rather than their sum, and the two reset tests, which differed only in whether the callback returns an error, became one table test. The assertions no longer compare `time.Now()` against an exact deadline: each callback stamps itself when it runs, and the tests fail only when the callback arrives earlier than a landed reset allows. Scheduler jitter can only delay a callback, and every wait is bounded at 4x the interval with a buffered callback channel, so a loaded runner stays green while a timer that stops calling back fails inside that bound instead of hanging the package. Each test stops its timer on the way out, unless the test has already failed: then the countdown may be the reason, its startTimer may be wedged, and StopTimer would block instead of letting the failure surface. All four cost under 4s now, the line XinFinOrg#2947 draws for a -short carve-out, so they lose their testshort.Skip calls and start running in -short, which is what `make quick-test` uses. The CI race job has run the full suite since XinFinOrg#2912, where the gate was already a no-op. The package costs 38.0s full and 1.0s -short before, 1.2s both ways after.
Five timer tests here waited four to five seconds per assertion, so `make test`, which runs one package at a time, spent 38s on nothing but sleep in this package: the third slowest in the full run. The durations were picked to be human-observable, but nothing in the tests depends on their absolute size: the only requirement is that a mid-flight Reset lands before the timeout, or the callback fires at the original deadline and the test reports a reset that never happened. The only timing judgment left is on the early side: a callback that saw the mid-flight Reset must arrive after interval plus half of resetAt, which sits 100ms above the 500ms deadline a callback that missed the reset would hit, so scheduler jitter can only push it further away. Every other wait follows from the interval. The timer tests also run in parallel now, so the package costs its longest test rather than their sum, and the two reset tests, which differed only in whether the callback returns an error, became one table test. The assertions no longer compare `time.Now()` against an exact deadline: each callback stamps itself when it runs, and the tests fail only when the callback arrives earlier than a landed reset allows. Scheduler jitter can only delay a callback, and every wait is bounded at 4x the interval with a buffered callback channel, so a loaded runner stays green while a timer that stops calling back fails inside that bound instead of hanging the package. Each test stops its timer on the way out, unless the test has already failed: then the countdown may be the reason, its startTimer may be wedged, and StopTimer would block instead of letting the failure surface. All four cost under 4s now, the line XinFinOrg#2947 draws for a -short carve-out, so they lose their testshort.Skip calls and start running in -short, which is what `make quick-test` uses. The CI race job has run the full suite since XinFinOrg#2912, where the gate was already a no-op. The package costs 38.0s full and 1.0s -short before, 1.2s both ways after.
Five timer tests here waited four to five seconds per assertion, so `make test`, which runs one package at a time, spent 38s on nothing but sleep in this package: the third slowest in the full run. The durations were picked to be human-observable, but nothing in the tests depends on their absolute size: the only requirement is that a mid-flight Reset lands before the timeout, or the callback fires at the original deadline and the test reports a reset that never happened. The only timing judgment left is on the early side: a callback that saw the mid-flight Reset must arrive after interval plus half of resetAt, which sits 100ms above the 500ms deadline a callback that missed the reset would hit, so scheduler jitter can only push it further away. Every other wait follows from the interval. The timer tests also run in parallel now, so the package costs its longest test rather than their sum, and the two reset tests, which differed only in whether the callback returns an error, became one table test. The assertions no longer compare `time.Now()` against an exact deadline: each callback stamps itself when it runs, and the tests fail only when the callback arrives earlier than a landed reset allows. Scheduler jitter can only delay a callback, and every wait is bounded at 4x the interval with a buffered callback channel, so a loaded runner stays green while a timer that stops calling back fails inside that bound instead of hanging the package. Each test stops its timer on the way out, unless the test has already failed: then the countdown may be the reason, its startTimer may be wedged, and StopTimer would block instead of letting the failure surface. All four cost under 4s now, the line XinFinOrg#2947 draws for a -short carve-out, so they lose their testshort.Skip calls and start running in -short, which is what `make quick-test` uses. The CI race job has run the full suite since XinFinOrg#2912, where the gate was already a no-op. The package costs 38.0s full and 1.0s -short before, 1.2s both ways after.


Proposed changes
CI ran every test twice: the non-race
testsjob and the race detector covered the same 130 packages, so the non-race half asserted nothing the detector does not. The detector becomes the only job that tests: the whole suite under-race, split into 13 lanes that run in parallel, with a bound on every job and a FIFO queue per ref so a push is never dropped. The slowest test job goes from 450s to 123s.Summary
Bulk slices are a function of the import path (
cksum(path) % 4inbuild/shard.sh), so a new package moves one package instead of every boundary after it.engine_v2_tests(110 tests) andcore(459) are sliced by test name, enumerated per lane withgo test -list '^(Test|Example|Fuzz)', so a new test, example or fuzz target lands in exactly one lane with no table to maintain; a fuzz target runs its seed corpus in normal mode.cmd/XDC(44 tests) andrpcare single lanes;rpccannot share one because its 32 MiB websocket test trips the codec's 30s pong timeout under-p 4.staticreads the bulk lane count back from the workflow, checks the partition (build/shard.sh --check), and verifies every heavy package declares lanes1..nand that nothing outside the heavy list does (--check-groups).Watchdogs: each lane passes
-timeout 10m(4m for rpc) under a 20m (9m) step bound, so a hung test panics and prints its goroutine dump instead of being killed with the runner;static,lint,race-scopeand the Docker and kubectl steps each carry a job-level bound.Scope:
race-scopeskips the detector only for a Markdown-only diff that touches nothing undertestdata/; a failed fetch, an empty diff or a new branch runs it; renames are compared with--no-renames, so a moved source path still counts.dev-upgradeand tags always run it, because the deploy jobs wait for it.Queue:
concurrency: {group: ci-CI-<ref>, queue: max}keeps one run per ref and queues the rest FIFO, up to 100 deep. A newer push never cancels an older run, and every pull request has its own ref, so different pull requests never wait for each other.build/ci.go testforwards-runand-timeout, which the name slices need; the default stays 45m for a local full pass.Evidence
dev-upgradeat93899befe6: 20 jobs, slowestrace (bulk 2/4)450s, the ninetestsslices 127-296s, then the deploy chain 09:05:03 → 09:07:28.After: run 42028424944 on
8d0a349d0e: 21 jobs, slowest test lanerace (cmd XDC)123s (core 2/2106s, bulk lanes 36-56s,rpc66s), whole run 158s withlint156s andstatic85s alongside.make check-shardson this tree → 26 + 33 + 34 + 33 packages,4 shards cover 126 packages,ok: 3 group packages, every lane 1..n present. The 13 lanes run all 130 packages that have test files;core/vm/privacystays out because its tests carry//go:build privacytestsand no lane has ever run them (go test -tags=privacytests ./core/vm/privacyby hand).coreenumerates 459 cases, not 456: the inventory now includesExampleGenerateChainand the twochainconfigfuzz targets, whose seed corpora ran under the oldtestsjob and would otherwise have stopped running.make race-test-full-group GROUP_PKG=./core GROUP_I=1 GROUP_N=227s;go run build/ci.go testforwarding;make generate,make tidy,make lint(0 issues),go vet ./...,make quick-test,make testall rc=0.race-scopereads the whole changed list before deciding, so a diff past the 64 KiB pipe buffer cannot flip the decision to "skip". The earliergrep -qform did exactly that (measured: 2.19 MB of changed paths →run=false).build/shard.sh --checkfails on a package listed twice;--check-groupsfails when a heavy package loses its lanes or a package outsideRACE_HEAVYclaims one.Merge Danger
Door: two-way. CI configuration only; reverting restores the previous topology, with no state to migrate and no binary to roll back.
Blast Radius: CI only, but five things to know before merging.
tests (...)check name is gone, and the race lanes changed (race (bulk 1/4..4/4),race (cmd XDC), plus the newrace-scopeandstatic). Scripts that grep the old names need updating.mergeStateStatus=BLOCKEDwith every check green and three approvals. Repository-level required checks are empty, the one repository ruleset has no rules, CODEOWNERS does not cover.github/,Makefileorbuild/, and the branch is up to date. Likely an organisation-level ruleset the public API cannot show; confirm with an administrator before merging.testdata/; ondev-upgradeand tags it always runs because the deploy jobs wait for it.queue: max, up to 100 deep): a run starts only after the previous run of the same ref has finished, and none is cancelled or replaced. A pull request has its own ref, so different pull requests never queue behind each other; a full queue cancels the run that arrives 101st.Types of changes
What type of changes does your code introduce to XDC network?
Put an
✅in the boxes that applyImpacted Components
Which parts of the codebase does this PR touch?
Put an
✅in the boxes that applyChecklist
Put an
✅in the boxes once you have confirmed below actions (or provide reasons on not doing so) that