Skip to content

test(daemon): drive tracedecay journey tests by signals, not wall bounds - #4174

Merged
ScriptedAlchemy merged 4 commits into
masterfrom
fix/load-flakes-2b
Oct 17, 2026
Merged

ScriptedAlchemy merged 4 commits into
masterfrom
fix/load-flakes-2b

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4149

This is the crates/tracedecay half of #4149. #4167 is the daemon-service half. Three //crates/tracedecay:unit_test journeys raced wall clocks under the #4149 load recipe. Each now waits on the signal production gives it. The residual tests are recorded on #4149.

Fixes

Test Cause Before After (recipe, 30 runs) Still fails when
source_body_serves_while_the_released_catalog_cannot_rewarm The 1 s caller deadline measured only CPU share. Production reads with CodeGraphReadinessRequirement::Engine and never waits on the held catalog reason_code=tool_dispatch_deadline_exceeded ... tool 'tracedecay_source_body' exceeded its absolute deadline before commit (3 of 20) 30/30 source_body is switched to CodeGraphReadinessRequirement::Catalog: fails in 11.4 s, tool failed
daemon_admission_remains_ready_while_lcm_indexes_converge_in_background A 10 s timeout around admission and the convergence gate store_runtime_tests stalls (issue item 3) 30/30 admission is made to wait for convergence Complete: TIMEOUT
shut_down_composition_releases_its_session_stores_for_an_immediate_reopen settled_session_ingest retried only application.runtime.mounting. The reopened composition answered temporal_refresh_unavailable, which is retryable: true, retry_after_millis: 250, and the helper returned it as settled the reopened composition must remount the session stores: ... Problem: temporal_refresh_unavailable ... Retryable: true (7 of 20) 30/30 across project_server_capacity_journey_test unchanged assertion on accepted_for_replay and on WAL bytes

The helper now retries any problem with structuredContent.problem.retryable == true. Its 30 s wall bound is removed, so a server that never settles fails at the harness timeout.

Admission time under load

The earlier lane saw project_sessions admission take 135 ms alone and 1.2 to 7 s under load. Phase probes in mount_project_session_store show where the time goes. Opening the store takes about 10 ms. attach_registered takes the rest: about 110 ms of wall time, which matches 11 to 13 ticks of process CPU time. Admission is CPU-bound, with no sleep or lock wait, so it scales with CPU starvation. No production change was made.

Verification

  • //crates/tracedecay:unit_test filtered to project_server_capacity_journey_test: 30/30 under the recipe.
  • //crates/tracedecay:unit_test filtered to source_body_readiness_journey_test and store_runtime_tests: 30/30 under the recipe.
  • bazel build --config=clippy //crates/tracedecay:unit_test: clean.
  • Merged with origin/master at 30c23ba.

Devin Review

source_body reads with the Engine requirement and never waits on the held
catalog, so its 1 s caller deadline only measured CPU share. The admission
test awaited admission under a 10 s timeout; it now awaits admission and
then the convergence gate, so an admission that waited on convergence
hangs instead of racing a wall clock.
settled_session_ingest retried only application.runtime.mounting. Under
load the reopened composition answered temporal_refresh_unavailable,
which is retryable after 250 ms, and the helper returned it as settled.
It now retries any problem marked retryable and drops its 30 s wall, so
a server that never settles fails at the harness timeout.
@changeset-bot

changeset-bot Bot commented Oct 17, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c49608d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Devin Review

Comment on lines +538 to 541
if answer["structuredContent"]["problem"]["retryable"] != true {
return answer;
}
tokio::time::sleep(std::time::Duration::from_millis(250)).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Permanent ingest refusals stall journey tests

If the refresh owner stays unavailable, settled_server_session_ingest retries its retryable refusal without a bound. The session journeys run until Bazel kills the whole test target instead of reporting the failed ingest.

Learn more

The session journeys use this helper to wait for a mounted project to finish ingesting. A missing or permanently failed refresh owner can return temporal_refresh_unavailable with retryable: true on every call through join_hook_ingest_refresh. Removing the previous 30-second bound leaves no way for the helper to identify that failure locally; the Bazel test target is size = "large" and times out as a whole. The repository rules require tests to stay falsifiable and forbid weakening assertions or raising timeouts.

Example: If the project's refresh owner remains unavailable, each ingest answers temporal_refresh_unavailable; the helper sleeps 250 ms and repeats until Bazel terminates the suite, rather than failing this journey with its last refusal.

Recommended fix: Keep a bounded, diagnostic failure path around the retry loop while allowing transient retryable refusals. Report the last typed problem when the bound is exhausted.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1181 to +1185
let database = registry
.project_sessions(project_id, [project_root])
.await
.expect("registered project sessions");
convergence_gate.wait_until_blocked().await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Admission regressions hang the test target

If project_sessions waits for convergence, its await cannot finish while convergence_gate remains held. Removing the deadline makes Bazel time out the entire test target instead of failing this admission check.

Learn more

This journey checks that foreground admission does not wait for background schema convergence. The convergence worker waits inside block until this test calls release() after its assertions. If a regression makes admission await that worker, the unbounded project_sessions call never returns and cannot reach release(). Removing the old timeout weakens this failure check despite the repository's instruction to keep tests falsifiable and not weaken assertions.

Example: If admission starts awaiting convergence, convergence holds at the gate, and the test holds at project_sessions; neither reaches release(). Bazel eventually aborts every test in the target instead of naming this failed admission.

Recommended fix: Retain a sufficiently measured finite admission guard that fails with a targeted diagnostic, while using the gate to assert that admission does not require convergence to finish.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@ScriptedAlchemy
ScriptedAlchemy merged commit 2a91469 into master Oct 17, 2026
10 of 20 checks passed
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.

flaky: remaining load-sensitive daemon tests

1 participant