End every goroutine serve starts before serve returns, so no loop outlives its controller (issue 1945)

The try of the on-change mirror failed with every test passed: goroutines a
served test started outlived its serve and met the next test's globals and its
closed stores. Under -race the served tests show it as a race on conditionsFrom
(watchTheMesh writing it while settlingPending, already running, read it), and
a take-in cut by the stop raised fingerprint-not-composed, a kind with no plain
words, which fails the package after its PASS line.

- serve counts every goroutine it and its parts start (serving_group.go) and
  ends and waits for them before anything else is undone: the lease's watch,
  the pending settler, the status summary, the facts, the mirrors and the new
  declared-packages keeper with its two bucket watches, the declaration sweep,
  the read-call counter, the watches' closers, the have watches, the vault move
  on a report, and the watchdogs, doctor, healers and asker under their own
  group.
- The condition store opens before anything that reads conditionsFrom starts;
  askerFrom is an atomic pointer, read from the keeper's goroutines.
- A done key's take-in cut by the stop raises nothing, and fingerprint-not-composed
  has plain words.
This commit is contained in:
2026-10-25 16:30:58 +01:00
parent a4cd5ba375
commit c75d726205
15 changed files with 159 additions and 48 deletions
+5 -2
View File
@@ -194,6 +194,9 @@ func followDoneKey(ctx context.Context, open *stores, module string) {
}
continue
}
if ctx.Err() != nil {
return // stopped: a take-in cut by the stop is no fault of the build, and the next controller takes it in
}
if keeper := conditionsFrom; keeper != nil && !failed {
o.Summary += err.Error()
if _, oerr := keeper.Observe(ctx, o); oerr == nil {
@@ -308,10 +311,10 @@ func watchDoneKeys(ctx context.Context, open *stores) {
fmt.Printf("done keys: the builder's keys are not watched: %v\n", err)
return
}
go func() {
goServing(ctx, func() {
<-ctx.Done()
closeKeys()
}()
})
// And every module once now, after the watch is open so no write falls between the two.
inBackground(func() {
doneKeysAtStart(ctx, catalogedModules(open), func(module string) { takeDoneKeyAtStart(ctx, open, module) })