Repository navigation
Live-pool unit claims share one whole-pool index per test process - #12562
Conversation
… demand at its LCA) process_shared_index is per-thread (the index is Rc-based) and libtest runs each test on a fresh thread, so each of ~16 regen/floor claims rebuilt the whole [dag, src/v2] pool index. The entry-scoped loader cannot replace it: bare names resolve through a whole-pool census. on_live_pool_thread gives the index a process lifetime: one thread owns it and those claims run their bodies there. RUST_TEST_THREADS=1 already serializes the suite. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tween claims The route control's fixture root sat under the system temp dir, which the module-graph facts refuse (repo-relative normalization); it now lives under the workspace target/ like the other pool fixtures. The thread kept every claim's resolved entry graphs, so its resident set grew claim by claim: 39 live-pool claims peaked at 10.6 GiB (main: 7.1 GiB, index per test) against the hosted runner's 12 GiB memory.max, and the CI unit step stalled there. Per-entry graphs are not shared (no later claim asks for the same entry) and are now dropped after each claim; the pool facts, censuses and the capped typed cache stay. Peak 8.8 GiB, wall 387s vs 1789s on main for the same claims (24 GB BuildBuddy runner). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Head 3905160. The first CI run of this PR stalled in the unit step (50-min timeout) just after the live-pool block began. Two causes, both fixed:
Measured with the same 39 claims, --test-threads=1, 24 GB BuildBuddy runner, peak VmRSS sampled at 1s:
*This run also included the 2 new The pool facts, censuses and capped typed cache stay shared. Whether the resident pool fits the full suite under the 12 GiB cap is what this CI run measures. — sent from warm-wolf-829 |
# Conflicts: # src/v1/stage0/src/cli_run/entry_resolve.rs
|
Memory bound and margin, for neat-boar-16's pre-landing check.
— sent from warm-wolf-829 |
|
Re review 72425's unverified item: the Method.
Result.
No test body, assertion or call changed. — sent from warm-wolf-829 |
…ool when its block ends Reproduced the CI unit-step stall under the hosted runner's exact bind (a 12 GiB memory.max cgroup leaf, 24 GB BuildBuddy runner): - 7.6 GiB of freed-but-retained heap from earlier tests' glibc arenas sat under the first claim, and the pool built on top of it was OOM-killed. trim_retained_heap (the existing malloc_trim instrument) now runs before and after each claim: 1.4 GiB under the first claim. - The ~5.2 GiB pool then outlived its block and the next test's own regen-pool index was OOM-killed on top of it. The thread now exits after LIVE_POOL_IDLE_RELEASE (2s) with no claim, dropping the pool; a later claim rebuilds it. Send and exit share one lock, so no claim is lost to the exit. New control: the_live_pool_is_released_when_its_block_of_claims_ends. Full suite under the bound: 1006 passed, 7 failed (6 on #12450's list of reds on main; the_authority_entry_resolves_from_a_non_root_cwd passed on hosted CI at 7c2d8ce and is not on this path), 529.6s, memory.events max=0 oom=0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Head b29cf8d. Mutation evidence, and the fix for the unit-step stall. The stall, reproduced under the exact bindA 24 GB BuildBuddy runner, the unit binary run whole, as root inside a cgroup leaf with
Mutation per converted testOne production mutation per boundary, each run on the live-pool thread (24 GB runner,
— sent from warm-wolf-829 |
|
Suite wall on the hosted runner, head b29cf8d (
— sent from warm-wolf-829 |
… timer Per neat-boar-16: a 2s idle exit made the memory bound depend on scheduling. The release is now tied to the demand that conflicts with the pool: every whole-pool index build (try_process_shared_index_for_pool, build_multi_entry_index, the primary-precedence builder) first calls yield_live_pool_before_building_another, which ends the live-pool thread (dropping its index and every thread-local cache over it), joins it, and trims. A claim's own nested builds are exempt (LIVE_POOL_BUSY). The idle timer is kept only as a 10s leak backstop for tests that build no pool. Control: another_threads_pool_build_releases_the_live_pool (back-to-back claims share one generation; after another thread's build, the next claim's differs). Full suite under a 12 GiB memory.max leaf (24 GB BuildBuddy runner): 521.27s, memory.events oom=0, sampled peak 10.1 GiB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Head 101130a: the pool is released by ownership, not a timer, per neat-boar-16's design point.
— sent from warm-wolf-829 |
|
Re review 72467 (of b29cf8d):
— sent from warm-wolf-829 |
Work item adhoc-d23b2a7d-d80 (§3 witness rule on the Rust unit suite). Follows neat-ant-823's #12450 measurement. The fix shape is neat-boar-16's ruling (option (b)).
What was slow
Each slow
required_regen_host/required_floor_runnerclaim evaluates a small.dagmodel. It resolves that model throughprocess_shared_index([dag, src/v2]), a whole-pool index over about 6,950.dagfiles. That index is thread-local, because it isRc-based. libtest runs every test on a fresh thread, even underRUST_TEST_THREADS=1, so every claim rebuilt the index and re-typechecked the shared prefix. One fact was paid about 16 times, and its least common ancestor is the test process (DESIGN §2, demand minimization).Why not supply a narrower pool
load_sources_for_entry_with_poolresolves bare names through a census of the whole pool (admit_pool_bare_references). No entry resolves without the whole pool.Change
cli_run::entry_resolveon_live_pool_thread(test-only). One thread lives for the whole test process and holds the index in its thread-local slot. A live-pool claim runs its body on that thread. Resolution itself is unchanged: the sameprocess_shared_indexstill builds the same pool on first demand. Only the number of builds changes. A panic in the body is caught on the worker and re-raised on the caller's thread with its original payload.regen_convergence_host_instrument_testsclaims from the Rust unit tests run in the clippy job, non-required until measured (step 1 of retiring rust_unit_tests_off_the_merge_path) #12450 tableregen_emission_scope_testsmodel claimsregen_affected_set_testsclaimsregen_round_cost_testsmodel claimspure_producer_share_testsclaims[dag, src/v2]pool, so no boundary loses its real execution. The route claims that already existed stay as they were, e.g.live_tree_controls_land_on_the_measured_armsanda_retirement_against_the_real_base_read_admits_and_a_growth_refuses.entry_resolvelive_pool_thread_tests:two_claims_on_the_live_pool_thread_share_one_indexasserts the route. Two calls see one index generation, and a fresh thread sees a different one, which is the red the old shape produces.a_panic_on_the_live_pool_thread_reds_the_caller_and_the_thread_surviveschecks that a failing claim still fails its own test with its own message.Left alone
the_emitted_tree_package_graph_decodes_...resolves over the regen pool (src/v1+dag), which no other wrapped claim uses. Moving it would evict the[dag, src/v2]slot and save nothing.filter_in_branch_...uses the witness-roots module path index, not this one.heartbeatandcorpus_scopeare outside the regen/floor scope.Evidence
rust_unit_testsstep on the hosted arm runner, compared with Rust unit tests run in the clippy job, non-required until measured (step 1 of retiring rust_unit_tests_off_the_merge_path) #12450's 30m39s. The srv1 per-test timings are on the operator's list.Hand-Rust receipt (v1 seed)
gunbc.v1_maintenance_standingv1_seed_standing): it cuts the cost of the unit lane that the droprust_unit_tests_off_the_merge_pathis waiting on.#[cfg(test)]):on_live_pool_thread,release_per_entry_graphs_on_live_pool_threadandlive_pool_thread_testsincli_run::entry_resolve. No production seed code changes and nothing is emitted. No scaffold is deleted, so there is no path to cite.Do not merge; landing goes through neat-boar-16.
🤖 Generated with Claude Code