Skip to content

Stop the chosen Ethereum node test timing out after a fresh build - #1328

Open
takemiyamakoto wants to merge 1 commit into
masterfrom
hotfix/ethereum-node-choice-test
Open

takemiyamakoto wants to merge 1 commit into
masterfrom
hotfix/ethereum-node-choice-test

Conversation

@takemiyamakoto

Copy link
Copy Markdown
Contributor

Summary

ChainRegistryTests/testChosenEthereumNodeTakesEffectInSessionAndAutomaticSelectionResumesOnTheNodeMovedTo failed the Core Data release gate (scripts/ci/run-coredata-release-gate.sh, Release -O) in runs that started right after a fresh build. It took 10.9 s and 11.1 s against its 10 s waits, and 8.07 s once on the 4.2.2 line. On reruns from the same build it took 0.04–1.1 s.

The registry was never the slow part, and this PR changes only the test file.

What was slow

With temporary timing probes (not in this PR), on a fresh simctl created iPhone 17 / iOS 27.0 simulator, building with the gate's settings and running ChainRegistryTests right after a cold build:

  • The registry applied the chosen-node update 0.5 ms after the test sent it: write barrier, pool reset, URLSession and provider.
  • The test's wait still took 5.5 s, because one RunLoop.current.run(until: +10 ms) call in waitForValue took 5.497 s. On a rerun from the same build, that turn took 0.072 s.
  • A 5 ms thread sample of the test host shows where that turn went: 936 main-thread samples in -[XCTestCase recordIssue:] → XCTInProcessSymbolicationService → CoreSymbolication building symbol data from the fresh binaries' debug maps (extract_debug_map_data_from_header: 482 samples). The rerun spent 12 samples there.

The issue XCTest was recording is the Thread Performance Checker's priority-inversion report from line 1035, waitUntilObserverAttached(). There the test thread blocks on an NSCondition that the StreamableProvider signals from its utility-QoS queue. Every gate log has that warning. XCTest records it from the main run loop, so the symbolication lands in the test's next wait. The deadline then passes inside a single run-loop turn, and waitForValue failed without looking at the state again.

A controlled check on the same probe build confirms this failure mode. An 11 s main-thread stall, queued right after the update was sent, makes master's waitForValue time out (11.63 s, one poll), although the registry applied the change 0.6 ms after the send. With this PR's helper the wait passes.

Change (test-only, ChainRegistryTests.swift)

  • waitForValue reads the state before it checks the deadline, so a run-loop turn that outlasts the budget cannot fail a wait the registry met meanwhile. The budget is unchanged: a condition still unmet at the deadline fails as before.
  • ChainRegistryTestObservable.waitUntilObserverAttached() polls on the run loop instead of blocking on the provider's queue (NSCondition → NSLock), so it no longer raises the checker's report.

Validation (local; this project has no CI gate)

Three Core Data gate runs, each right after a full fresh build (DerivedData removed first; 3,206 compiles), on a fresh iPhone 17 / iOS 27.0 simulator, at local validation commit 7b3b9b175. That commit is #1327's head 2668d3624 plus this commit, tree 56f4998b. Master's own gate cannot pass yet; see Merge order.

Run Gate This test #1327's socket-state test
1 PASSED, 483/483, identities equal the list 0.059 s 7.99 s
2 PASSED, 483/483, identities equal the list 0.062 s 17.52 s
3 PASSED, 483/483, identities equal the list 0.065 s 0.73 s

With this change, the gate's only runtime warning comes from #1327's waitUntilBlocked(). The first-issue symbolication therefore lands in testSocketStateChangeDuringChainUpdateReadsTheListOnlyAfterTheUpdate's next waitForCondition, as one long run-loop turn. In run 2 that turn took about 17 s against a 10 s budget. Master's waitForValue would have failed the test there; this PR's passed it.

PR head on master (87cd4d75b, tree a95e576f): the gate's xcodebuild ran 478 tests, 478 passed, 0 failed, 0 skipped, with no runtime warnings. The gate then failed only master's existing inventory check ("expected exactly 424 tests, found 478"), which fails on master without this change too.

Evidence, scripts, probe diffs, thread samples and logs are in the local report build/reports/ios-ethereum-node-test-20261113/ (Xcode 27.2 27B52).

Merge order

Follow-up after #1327

#1327 adds BlockingConnectionFactory.waitUntilBlocked(), which blocks on a DispatchSemaphore signalled from the registry's write. It raises the same checker report, so after #1327 the first-issue symbolication lands in testSocketStateChangeDuringChainUpdateReadsTheListOnlyAfterTheUpdate, where this PR's waitForValue absorbs it. Making that helper poll too would leave ChainRegistryTests with no runtime warnings at all. That change belongs on #1327's code, so it is not in this PR.

🤖 Generated with Claude Code

testChosenEthereumNodeTakesEffectInSessionAndAutomaticSelectionResumesOnTheNodeMovedTo
failed the Core Data release gate in runs that started right after a fresh build
(10.9 s and 11.1 s against its 10 s waits) and passed on reruns from the same build.

The registry was never slow: with timing probes it applied the chosen-node update
0.5 ms after the test sent it. The time went into one RunLoop.run(until: +10 ms)
call in waitForValue, which took 5.5 s after a cold build and 0.07 s on a rerun.
In that run-loop turn XCTest recorded the Thread Performance Checker's report from
waitUntilObserverAttached (the test thread blocked on the provider's utility-QoS
queue) and symbolicated it in process, building symbol data from the fresh
binaries' debug maps. The deadline passed inside that turn, and the loop failed
without looking at the state again.

- waitForValue reads the state before it checks the deadline, so a run-loop turn
  that outlasts the budget cannot fail a wait the registry met meanwhile.
- The test observable's attach wait polls on the run loop instead of blocking on
  the provider's queue, so it no longer raises the report.

Test-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@takemiyamakoto
takemiyamakoto requested a review from a team as a code owner November 13, 2026 15:02
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