Skip to content

fix(ci): remove the unit-test data race and shard the job under 10 minutes - #1328

Merged
33cn merged 3 commits into
masterfrom
fix/ci-unit-test-race-and-runtime
Oct 14, 2026
Merged

33cn merged 3 commits into
masterfrom
fix/ci-unit-test-race-and-runtime

Conversation

@vipwzw

@vipwzw vipwzw commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

问题

PR #1327 上 ci_base / unit-test 失败(Failing after 13m),日志根因是一条数据竞争(-race 报错),不是断言失败:

WARNING: DATA RACE
Write at ... by goroutine 34:
  types.(*Forks).setFork()  <-  types.(*Chain33Config).SetFork()
  plugin/dapp/paracross/testnode.NewParaNode()   paranode.go:32
Previous read ... by goroutine 33122:
  types.(*Forks).GetFork()  <-  types.(*Chain33Config).IsFork()
  types.(*Block).Hash()  <-  blockchain.(*BlockStore).LastHeader()
  blockchain.(*BlockChain).ProcRecvMsg.gowrap8()
--- FAIL: TestParaNode (30.25s)   plugin/consensus/para

testnode.NewWithConfig 会立即启动 blockchain 等后台 goroutine,它们持续读配置里的 Forks(Block.Hash() -> cfg.IsFork());而 NewParaNode 在节点启动之后才写 Forks(SetFork("ForkParaFee", MaxHeight))。chain33 的 Forks 没有锁,于是形成数据竞争,在 -race 下随机失败(本地偶然、CI 上必然偶发)。

修复

  1. 竞争修复:把 ForkParaFee 的覆盖移到 testnode.NewWithConfig 之前——此时配置还是单线程状态,写 Forks 安全。行为不变(ForkParaFee 仍为 MaxHeight 禁用,避免测试模式下 para 链收手续费破坏共识)。
    新增回归测试 plugin/dapp/paracross/testnode/paranode_fork_test.go 固化这个约定(断言覆盖在节点创建后依然生效),并在 -race 下覆盖节点创建路径。

  2. 把 unit-test 压到 10 分钟以内:原来单 job 跑 make test(go test -parallel=8 -race 全量 ~370 个包),实测约 13 分钟。现拆成 4 个并行分片(按包序号取模,不维护硬编码包列表,新增包自动落入某片),并保留一个同名聚合 job unit-test,因此分支保护里 Required check 的名字不变。

    • -race 全量保留,覆盖不缩水;
    • 分片覆盖已验证:354 个包 → 89/89/88/88,并集与全集一致、无重复;
    • 预期 wall time ≈ 4~6 分钟。

验证

  • plugin/dapp/paracross/testnode 新增测试:本地 go test -race -run TestParaNodeForkParaFeeDisabled → ok 9.48s,无 DATA RACE。
  • 本地复现说明:plugin/consensus/para/TestParaNode 在本机(macOS)有既存的环境性失败(未改动的 master 代码同样在第 37 行断言失败),因此该用例的最终验证依赖 CI。
  • YAML 语法与分片脚本已本地校验通过。

后续

king added 3 commits October 14, 2026 14:00
testnode.NewWithConfig immediately starts the blockchain and other
background goroutines, which read the config's Forks map (Block.Hash ->
cfg.IsFork). chain33's Forks has no lock, so writing it afterwards - as
NewParaNode did for ForkParaFee - is a data race with those goroutines
and randomly fails TestParaNode under -race (seen in CI:
plugin/consensus/para, 'WARNING: DATA RACE', GetFork vs setFork).

Move the override before the node is created so the map is only written
while the config is still single threaded; behaviour is unchanged
(ForkParaFee stays disabled). Add a regression test pinning that the
override survives node construction.
make test runs go test -race over ~370 packages and took ~13 minutes on
a single 4 vCPU runner. Split the packages by index modulo across four
parallel shard jobs so wall time drops under 10 minutes, and keep a
unit-test aggregator job with the exact same name so the required
branch-protection check keeps working. Race detection is unchanged
(still -race, all packages).
The first version created real nodes and closed them immediately, so
background initialization hit ErrChannelClosed and panicked; that also
failed the coverage and Test GOARCH 386 jobs. Extract the config
construction into newParaConfig (all config writes, including Forks,
now happen before any node exists) and assert that contract without
starting a node, so the test cannot flake on node start/stop ordering.
@33cn
33cn merged commit ccc9ee6 into master Oct 14, 2026
25 checks passed
@33cn

33cn commented Oct 14, 2026

Copy link
Copy Markdown
Owner

🎉 This PR is included in version 1.75.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@33cn 33cn added the released label Oct 14, 2026
vipwzw pushed a commit that referenced this pull request Oct 14, 2026
上一轮把 ci_cross2eth(37m->8m50s)、ci_bridgevmxgo(17m->7m16s) 压下来之后,
还剩三个 job 超过 10 分钟(PR #1330 实测): coverage 17m25s、unit-test 13m10s、
Test GOARCH 386 10m01s; ci_evm(9m37s)/ci_paracross(9m33s) 也几乎没有余量。
unit-test 已由 #1328 分 4 片解决, 本 commit 解决剩下的部分。

1) coverage 17m25s -> 预期约 6 分钟
   build/tools/coverage.sh 原来用 bash for 循环**逐包串行** go test: 126 个有测试的包,
   单包最慢约 2 分钟(oracle/rpc 112s、relayd 91s、lightclient btc 91s、dpos 89s、
   ticket/executor 60s), 串行合计 991s。go test 从 1.20 起支持一次对多个包生成合并的
   覆盖率文件, 并且自己按 -p 并行跑包, 改成一次调用后墙钟时间降到"最慢的包 + 排队"量级。
   覆盖率语义不变(仍是 -covermode=count 的合并 profile, 文件名 coverage.cov 不变)。

2) Test GOARCH 386 10m01s -> 预期约 8m50s
   Build 步骤 147s 里约 72s 是 `make build` 的 depends —— 串行编译几十个 dapp 的 cmd 产物,
   而这个 job 只需要验证 386 下能编译、能跑测试。新增 `make build_only`
   (只编 chain33/chain33-cli/fork-config), 386 job 改用它; `build` 保持原语义
   (build: depends build_only), 只是把编译部分抽出来复用。

3) 定点构建: ci_evm / ci_rgbx / ci_mix / ci_relay / ci_parachain_rollup 各约省 40s
   这些 workflow 原来要么只跑 `make docker-compose dapp=X`(由 docker-compose.sh 的
   `if ! [ -d ci ]` 兜底触发整个 make), 要么直接 `make build_ci`, 都会 depends 编译所有
   dapp。统一改成上一轮引入的 `make build_ci_dapp dapp=X`。
   **ci_paracross 不能这么改**: 它会走 rpc_test -> dapp_test_rpc, 后者固定执行
   ./ticket/test-rpc.sh 以及 build/ci/dapptest 下所有 dapp 的 test-rpc.sh, 仍然需要
   depends 的完整产物; 只编 paracross 会因缺少 ticket/test-rpc.sh 而失败(已在 CI 复现,
   现已在 workflow 里写明原因)。

4) depends 并行化: 每个 dapp 的 cmd/build.sh 只写自己的 build/ci/<dapp> 与
   build/ci/dapptest/<dapp>, 互不重叠, 把 `find -exec ... \;` 换成 `xargs -P4`
   (DEPENDS_JOBS 可覆盖)。对仍需完整 depends 的 ci_paracross/ci_base/check_fmt/autotest
   各约省 30~40s。

验证方式与上一轮相同: 全部改动由 CI 实跑确认每个 job <10 分钟且测试全绿。
vipwzw pushed a commit that referenced this pull request Oct 14, 2026
上一轮把 ci_cross2eth(37m->8m50s)、ci_bridgevmxgo(17m->7m16s) 压下来之后,
还剩三个 job 超过 10 分钟(PR #1330 实测): coverage 17m25s、unit-test 13m10s、
Test GOARCH 386 10m01s; ci_evm(9m37s)/ci_paracross(9m33s) 也几乎没有余量。
unit-test 已由 #1328 分 4 片解决, 本 commit 解决剩下的部分。

1) coverage 17m25s -> 实测 4m58s
   build/tools/coverage.sh 原来用 bash for 循环**逐包串行** go test: 126 个有测试的包,
   单包最慢约 2 分钟(oracle/rpc 112s、relayd 91s、lightclient btc 91s、dpos 89s、
   ticket/executor 60s), 串行合计 991s。go test 从 1.20 起支持一次对多个包生成合并的
   覆盖率文件, 并且自己按 -p 并行跑包, 改成一次调用后墙钟时间降到最慢包的量级。
   覆盖率语义不变(仍是 -covermode=count 的合并 profile, 文件名 coverage.cov 不变)。

2) Test GOARCH 386 10m01s -> 用 build_only
   Build 步骤 147s 里约 72s 是 `make build` 的 depends —— 串行编译几十个 dapp 的 cmd 产物,
   而这个 job 只需要验证 386 下能编译、能跑测试。新增 `make build_only`
   (只编 chain33/chain33-cli/fork-config), 386 job 改用它; `build` 保持原语义
   (build: depends build_only), 只是把编译部分抽出来复用。

3) 定点构建: ci_evm / ci_rgbx / ci_mix / ci_relay / ci_parachain_rollup 各约省 40s
   这些 workflow 原来要么只跑 `make docker-compose dapp=X`(由 docker-compose.sh 的
   `if ! [ -d ci ]` 兜底触发整个 make), 要么直接 `make build_ci`, 都会 depends 编译所有
   dapp。统一改成上一轮引入的 `make build_ci_dapp dapp=X`。
   **ci_paracross 不能这么改**: 它会走 rpc_test -> dapp_test_rpc, 后者固定执行
   ./ticket/test-rpc.sh 以及 build/ci/dapptest 下所有 dapp 的 test-rpc.sh, 仍然需要
   depends 的完整产物; 只编 paracross 会因缺少 ticket/test-rpc.sh 而失败(已在 CI 复现,
   workflow 里已写明原因)。

4) depends 并行化 + 修掉两个一直静默失败的 build.sh
   每个 dapp 的 cmd/build.sh 只写自己的 build/ci/<dapp> 与 build/ci/dapptest/<dapp>,
   互不重叠, 把 `find -exec ... \;` 换成 `xargs -P4`(DEPENDS_JOBS 可覆盖),
   对仍需完整 depends 的 ci_paracross/ci_base/check_fmt/autotest 各约省 30~40s。
   注意 find 的 `-exec` 失败只让谓词为假、find 本身仍返回 0, 所以 evmxgo/lightclient
   的 build.sh 里 `cp ./build/*`(这两个 dapp 根本没有 build 目录)失败了很多年都没人发现;
   换成 xargs 后失败会透出, 已把这两处改成显式判断目录存在, 让 depends 能对真正的错误
   fail-fast。

验证方式与上一轮相同: 全部改动由 CI 实跑确认每个 job <10 分钟且测试全绿。
33cn pushed a commit that referenced this pull request Oct 15, 2026
## [1.75.2](v1.75.1...v1.75.2) (2026-10-15)

### Performance Improvements

* **ci:** bring every PR job under 10 minutes ([92b6876](92b6876)), closes [#1330](#1330) [#1328](#1328)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants