From 96c7b4537223b76a0acae1841fce1f217a749067 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sun, 18 Oct 2026 05:15:16 +0000 Subject: [PATCH] test(code-index): wait for the worker's permit, not its phase The source-hint attribution test queued its own admission request once the worker reported AwaitingAdmission, then held the granted permit while waiting for the seat. The worker sets that phase before it first polls its permit request, on another runtime thread. When the test's request queued first it took the permit, and the worker that must seat the generation waited on the test: a deadlock, 5 of 40 runs under load. Release the only permit and wait until it is gone, which only the worker holding it can cause, then queue behind that source pass. Refs #4238 --- .../registry/cold_read_wake_tests.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/tracedecay-code-index-runtime/src/code_index_scheduler/registry/cold_read_wake_tests.rs b/crates/tracedecay-code-index-runtime/src/code_index_scheduler/registry/cold_read_wake_tests.rs index 240561e6db..6902ab4743 100644 --- a/crates/tracedecay-code-index-runtime/src/code_index_scheduler/registry/cold_read_wake_tests.rs +++ b/crates/tracedecay-code-index-runtime/src/code_index_scheduler/registry/cold_read_wake_tests.rs @@ -224,11 +224,18 @@ async fn source_hint_interrupts_attribution_and_owned_noop_retries_same_generati let root = canonical_existing_identity(&project).unwrap(); registry.request_complete_generation(&root).await; let mut activity = registry.subscribe_owner_activity(&root).await.unwrap(); - while activity.worker_phase() != super::CodeIndexWorkerPhaseV1::AwaitingAdmission { + // The worker sets `AwaitingAdmission` before it first polls its permit + // request, so that phase does not say the request is queued. Release the + // only permit and wait for it to be gone: the worker holds it, which no + // phase can say, and our request below queues behind the source pass. + drop(admission); + while registry + .background_reconcile_admission() + .available_permits() + != 0 + { activity.changed().await.unwrap(); } - // The current-thread runtime observes the phase after the worker yields - // on its admission future, so its semaphore request is already queued. // Queue behind the source pass but ahead of its optional preparation. // This holds the actual published memo cold, rather than constructing an // unrelated scheduler handle which residency may replace before seating. @@ -239,7 +246,6 @@ async fn source_hint_interrupts_attribution_and_owned_noop_retries_same_generati std::task::Poll::Ready(()) }) .await; - drop(admission); let admission = next_admission.await.unwrap(); let mut signals = super::owner_signals::CodeIndexOwnerSignalsV1::subscribe(®istry, &root).await;