Summary
reth-engine-tree::tree::payload_processor::tests::test_state_root is flaky under nextest package-level / CI unit shards:
- Test body completes successfully (
assert_eq! / libtest prints ok).
- Process then aborts with:
pthread lock: Invalid argument
(test aborted with signal 6: SIGABRT)
- nextest retries (default profile: 2 retries / 3 attempts total) often eventually pass, so CI can go green or red nondeterministically.
This is unrelated to tx-filter / gas packing work (e.g. PR #414). It can still fail the required unit / test / ethereum (*/*) jobs and force cancel of sibling shards.
Evidence
CI (PR #414 head 726ee11)
Local repro (full package, nextest)
Isolated single-test runs usually pass. Full package:
cargo nextest run -p reth-engine-tree --lib
Reproduces ABRT on test_state_root under concurrent package load (same post-ok pattern). Separately, tree::tests::test_tree_persist_blocks often hits the nextest 60s slow-timeout under the same full-package stress (may be independent; see Notes).
Suspected root cause
Lifecycle of background work in the legacy PayloadProcessor path:
WorkloadExecutor uses a process-global OnceLock tokio runtime plus a per-instance rayon pool (crates/engine/tree/src/tree/payload_processor/executor.rs).
spawn starts multiproof / sparse-trie / proof / prewarm / tx-iterator via spawn_blocking but drops JoinHandles (fire-and-forget).
test_state_root builds a temporary MDBX factory, drives state updates, awaits handle.state_root(), asserts, then drops handle → processor → provider → factory.
- Background tasks can still hold DB / trie resources while the test process tears down MDBX and static runtime → classic post-success
pthread lock + SIGABRT.
So this is a teardown race, not a wrong state-root value (the body already matched serial root).
Upstream reth (paradigmxyz/reth / local ../reth)
Not the same bug on the same code path.
Upstream has moved state-root off the old PayloadProcessor multiproof stack into state_root_strategy + reth_tasks::Runtime::test() (owned runtime per test, cancel guards). The equivalent test is roughly state_root_task_matches_serial_root. There is no identical payload_processor::tests::test_state_root + static WorkloadExecutor OnceLock path.
So: do not assume a one-line cherry-pick from tip reth; gravity still carries the older engine-tree shape.
Suggested directions (short → long)
- Short (targeted): Keep
JoinHandles on PayloadHandle and join them on drop (after terminating prewarm / dropping multiproof sender). Local experiment after this change: full-package runs showed stable PASS for test_state_root (no ABRT); verify production drop paths still OK (block_on / block_in_place when already inside a runtime).
- Short (CI hygiene): Optionally re-run failed unit jobs; nextest retries already paper over some flakes.
- Medium: Align with upstream —
Runtime::test() / proper cancel semantics for state-root tasks; avoid process-global OnceLock RT for tests.
- Do not couple this to product PRs (e.g. gas last-gate) unless the PR intentionally touches engine-tree.
Acceptance
Notes
- nextest default profile:
retries = { backoff = "exponential", count = 2, ... } and slow-timeout = { period = "30s", terminate-after = 2 } (effective ~60s kill) in .config/nextest.toml.
- Related noise under the same stress:
tree::tests::test_tree_persist_blocks timing out — may be independent load/deadlock; track separately if it persists after the ABRT fix.
Refs
Summary
reth-engine-tree::tree::payload_processor::tests::test_state_rootis flaky under nextest package-level / CI unit shards:assert_eq!/ libtest printsok).This is unrelated to tx-filter / gas packing work (e.g. PR #414). It can still fail the required
unit/test / ethereum (*/*)jobs and force cancel of sibling shards.Evidence
CI (PR #414 head
726ee11)unit / test / ethereum (1/2)https://github.com/Galxe/gravity-reth/actions/runs/30779756650/job/91581944771
1200 tests run: 1199 passed, 1 failedLocal repro (full package, nextest)
Isolated single-test runs usually pass. Full package:
Reproduces ABRT on
test_state_rootunder concurrent package load (same post-ok pattern). Separately,tree::tests::test_tree_persist_blocksoften hits the nextest 60s slow-timeout under the same full-package stress (may be independent; see Notes).Suspected root cause
Lifecycle of background work in the legacy
PayloadProcessorpath:WorkloadExecutoruses a process-globalOnceLocktokio runtime plus a per-instance rayon pool (crates/engine/tree/src/tree/payload_processor/executor.rs).spawnstarts multiproof / sparse-trie / proof / prewarm / tx-iterator viaspawn_blockingbut dropsJoinHandles (fire-and-forget).test_state_rootbuilds a temporary MDBX factory, drives state updates, awaitshandle.state_root(), asserts, then drops handle → processor → provider → factory.pthread lock+ SIGABRT.So this is a teardown race, not a wrong state-root value (the body already matched serial root).
Upstream reth (
paradigmxyz/reth/ local../reth)Not the same bug on the same code path.
Upstream has moved state-root off the old
PayloadProcessormultiproof stack intostate_root_strategy+reth_tasks::Runtime::test()(owned runtime per test, cancel guards). The equivalent test is roughlystate_root_task_matches_serial_root. There is no identicalpayload_processor::tests::test_state_root+ staticWorkloadExecutorOnceLock path.So: do not assume a one-line cherry-pick from tip reth; gravity still carries the older engine-tree shape.
Suggested directions (short → long)
JoinHandles onPayloadHandleand join them on drop (after terminating prewarm / dropping multiproof sender). Local experiment after this change: full-package runs showed stable PASS fortest_state_root(no ABRT); verify production drop paths still OK (block_on /block_in_placewhen already inside a runtime).Runtime::test()/ proper cancel semantics for state-root tasks; avoid process-global OnceLock RT for tests.Acceptance
cargo nextest run -p reth-engine-tree --libgreen across ≥5 consecutive full-package runs (no ABRT ontest_state_root).unitjob no longer fails solely on this post-success SIGABRT.test_tree_persist_blocks60s timeout remains under full-package stress.Notes
retries = { backoff = "exponential", count = 2, ... }andslow-timeout = { period = "30s", terminate-after = 2 }(effective ~60s kill) in.config/nextest.toml.tree::tests::test_tree_persist_blockstiming out — may be independent load/deadlock; track separately if it persists after the ABRT fix.Refs