Leave moving the plans after a build's outcome to the look in the serving controller, so a release's retried send no longer runs on the event loop (issue 1708)

This commit is contained in:
2026-10-21 14:39:17 +02:00
parent c24139a0fd
commit 61c8b56107
3 changed files with 61 additions and 1 deletions
@@ -104,3 +104,39 @@ func TestTheRecordsSettleAnOutcomeWithWhatTheBuildDid(t *testing.T) {
t.Fatalf("an outcome's words were kept for a second settle: %q", got)
}
}
// 1708/AC5: given a serving controller (its look at the plans running), when an outcome was taken into the plans,
// then moving them (a release's sends, which may retry for minutes) is left to the look, off the event loop
// (2026-10-21 14:28:40 to 14:32:10: the loop sent okioki's release to g14 after mesh-delivery-builder's outcome).
func TestAnOutcomeLeavesMovingThePlansToTheLook(t *testing.T) {
l := withLooker(t)
moved := false
advanceAfterOutcome(func() { moved = true })
if moved {
t.Fatal("the plans were moved on the event loop after an outcome, where a send that retries holds every message")
}
select {
case <-l.signal:
default:
t.Fatal("the look was not asked to move the plans after an outcome")
}
}
// 1708/AC5: given no look running (a command, which has no event loop), when an outcome was taken, then the plans are
// moved at once, as before.
func TestAnOutcomeMovesThePlansWhereNoLookRuns(t *testing.T) {
lookerMu.Lock()
before := theLooker
theLooker = nil
lookerMu.Unlock()
t.Cleanup(func() {
lookerMu.Lock()
theLooker = before
lookerMu.Unlock()
})
moved := false
advanceAfterOutcome(func() { moved = true })
if !moved {
t.Fatal("with no look running, an outcome left the plans unmoved")
}
}
+12
View File
@@ -136,6 +136,18 @@ func askToLook() {
}
}
// askedToLook asks the serving controller's look at the plans, and says whether there was one to ask.
func askedToLook() bool {
lookerMu.RLock()
l := theLooker
lookerMu.RUnlock()
if l == nil {
return false
}
l.ask()
return true
}
// startPlanLooks starts the look at the open plans, and asks for the first: the controller took the lease, and a
// plan left by a replaced controller resumes from there (ADR 0406 §6). It replaces the rollout's 30 second ticker.
// It answers a channel that is closed once the look has returned after the context ended, for a test that restores what
+13 -1
View File
@@ -761,7 +761,19 @@ func planBuiltSaying(ctx context.Context, open *stores, module, commit, failed s
fmt.Printf("%s: %s; the tiers after it are not asked\n", p.ID, p.Note)
}
}
advanceHeld(ctx, open)
advanceAfterOutcome(func() { advanceHeld(ctx, open) })
}
// advanceAfterOutcome moves the plans after an outcome was taken into them: **in the serving controller, by asking
// the look** (novox/hq issue 1708). The outcome is taken on the event loop, and moving the plans may send machines
// their declarations, a release's send retried for minutes among them; done here it held every report, build and
// merge behind it (2026-10-21 14:28:40 to 14:32:10). The look runs off the loop, holding the plans itself. Where no
// look runs (a command, which has no event loop) the plans are moved at once, as before.
func advanceAfterOutcome(advance func()) {
if askedToLook() {
return
}
advance()
}
// startedBeside is the id of a started walk open beside this one that it is related to — a merge's walk past