Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ the recovery watch does. What it still cannot hear locally is a compile that ran
(the type's grain placed on a serving replica) or a local FAILURE (only successes are published
locally) — those still time out as *unevaluated*, never as a verdict.

🚨 **"Compiled, its stamp is held" is announced BEFORE the stamp is held** (#6050). The witness fires
first, synchronously on the compile thread, and the stamp is offered to `MeshPublicationGate` a
moment later in the same callback — that ordering is the point of the witness, so it is pinned, not
"fixed". Consequently `MeshPublicationGate.HeldCount` read once, right after `WarmOne` answered,
can still be 0: anything that needs the hold (a test, a diagnostic) WAITS for it rather than sampling
it. `AGatedSweepHearsItsOwnCompileTest` asserts both halves — the gate's count read inside the
witness's own notification is unchanged, and the hold then arrives; swapping the two statements in
`NodeTypeCompilationHelpers` turns the first assertion red.

### The readiness message names the step it is waiting on

The gate's detail used to be set once — `enumerating dynamic NodeTypes` — before the bundle seeding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ public async Task AProvisionalSweep_ReportsCompiled_ThoughItsStampIsHeld()
bake.MarkRunning("enumerating dynamic NodeTypes");
bake.Admission.Should().Be(MeshAdmission.Provisional, "precondition — the bake gates and is measuring");

// 🚨 Core #6050 — PIN the ordering the hold-wait below exists for, deterministically. The
// witness (LocalNodeTypeBuilds.Built) is a synchronous Subject, so a read made INSIDE its
// OnNext runs on the compile thread at the exact instant the warm learns "Compiled" — and
// at that instant the stamp has not been offered to the gate yet
// (NodeTypeCompilationHelpers: RecordUsableBuild precedes admission.Publish). That gap is
// why a single HeldCount sample taken after the warm answered could read 0. Replay(1) is
// connected before the warm so the reading cannot be missed.
var gate = Mesh.ServiceProvider.GetRequiredService<MeshPublicationGate>();
var heldBefore = gate.HeldCount;
var heldWhenWitnessed = Mesh.ServiceProvider.GetRequiredService<LocalNodeTypeBuilds>().Built
.Where(p => p == typePath)
.Select(_ => gate.HeldCount)
.Take(1)
.Replay(1);
using var witnessed = heldWhenWitnessed.Connect();

var outcome = await Warm(typePath).Should().Within(Budget + TestTimeouts.Convergence)
.Emit("WarmOne always reaches exactly one outcome", TestContext.Current.CancellationToken);
Output.WriteLine("outcome: {0} — {1} — {2}", outcome.Status, outcome.Detail ?? "(no detail)", outcome.Duration);
Expand All @@ -115,7 +131,12 @@ public async Task AProvisionalSweep_ReportsCompiled_ThoughItsStampIsHeld()
// stamp …")` runs in that subscription) — so an instantaneous HeldCount read could land in
// between and see 0 (core #6132, shard 5: "Expected 0 to be greater than 0"). The hold is
// read first, so the record check below is made once the stamp is known to be held.
var gate = Mesh.ServiceProvider.GetRequiredService<MeshPublicationGate>();
var atWitness = await heldWhenWitnessed.Should().Within(TestTimeouts.Convergence)
.Emit("the compile is witnessed on this process", TestContext.Current.CancellationToken);
atWitness.Should().Be(heldBefore,
"the local witness fires BEFORE the compile stamp is offered to the gate — the product's "
+ "ordering, so the warm's Compiled can precede the hold and the hold must be WAITED for");

await Observable.Interval(100.Milliseconds()).StartWith(0L)
.Select(_ => gate.HeldCount)
.Where(held => held > 0)
Comment thread
Copilot marked this conversation as resolved.
Outdated
Expand Down