diff --git a/cmd/mesh-controller/chain.go b/cmd/mesh-controller/chain.go index e9169a01..18436e8b 100644 --- a/cmd/mesh-controller/chain.go +++ b/cmd/mesh-controller/chain.go @@ -46,29 +46,43 @@ const chainMoveRetry = 30 * time.Second // over the bus's largest — is raised as an urgent condition, and the tables stay what is read: nothing is half-switched. func movingTheChain(ctx context.Context, inv *inventory.Inventory) { say := func(format string, args ...any) { fmt.Printf("chain: "+format+"\n", args...) } - for { - _, err := inv.MoveChain(ctx, say) - var refusal *inventory.Refusal - switch { - case err == nil: - go clearChainMove(ctx) - go askAgainSoon(ctx, inv) - return - case errors.As(err, &refusal): - say("%v", err) - go raiseChainMove(ctx, refusal) - return - case ctx.Err() != nil: - return - default: - say("the move to the bus did not finish, and the tables are read until it does: %v", err) - } - select { - case <-ctx.Done(): - return - case <-time.After(chainMoveRetry): - } + // The first try is made here, before the controller serves; the tries after it, while it serves. + if movedOrStopped(ctx, inv, say) { + return } + go func() { + for { + select { + case <-ctx.Done(): + return + case <-time.After(chainMoveRetry): + } + if movedOrStopped(ctx, inv, say) { + return + } + } + }() +} + +// movedOrStopped makes one try at the move and says whether there is nothing more to try: moved, refused, or the controller +// ending. False for a failure of the kind that is tried again (the bus away, a write not granted yet, the lease not held). +func movedOrStopped(ctx context.Context, inv *inventory.Inventory, say func(string, ...any)) bool { + _, err := inv.MoveChain(ctx, say) + var refusal *inventory.Refusal + switch { + case err == nil: + go clearChainMove(ctx) + go askAgainSoon(ctx, inv) + return true + case errors.As(err, &refusal): + say("%v", err) + go raiseChainMove(ctx, refusal) + return true + case ctx.Err() != nil: + return true + } + say("the move to the bus did not finish, and the tables are read until it does: %v", err) + return false } // chainMoveObservation is the urgent condition for a refused move. @@ -100,7 +114,7 @@ func raiseChainMove(ctx context.Context, r *inventory.Refusal) { func clearChainMove(ctx context.Context) { whenKept(ctx, func(k *conditions.Keeper) { - for _, kind := range []string{inventory.KindBuilds} { + for _, kind := range []string{inventory.KindBuilds, inventory.KindRollouts, inventory.KindAssignments} { key := chainMoveObservation(&inventory.Refusal{Kind: kind}).Key() if _, err := k.Clear(ctx, key, "the "+kind+" are on the bus"); err != nil { fmt.Printf("chain: the condition for the move of the %s could not be cleared: %v\n", kind, err) diff --git a/internal/inventory/chain_move.go b/internal/inventory/chain_move.go index c97c36d3..a9afde08 100644 --- a/internal/inventory/chain_move.go +++ b/internal/inventory/chain_move.go @@ -248,24 +248,39 @@ func (i *Inventory) assignedWithSends(ctx context.Context) ([]chain.Assignment, if err := rows.Err(); err != nil { return nil, err } + // The build of each commit, read once: the newest successful build of the module at it. + type at struct{ module, commit string } + var all []Build + if _, moved, err := i.ChainReads(ctx, KindBuilds); err != nil { + return nil, err + } else if moved { + if all, err = chainBuilds(ctx, i.attached()); err != nil { + return nil, err + } + } else if all, err = i.allBuildRows(ctx); err != nil { + return nil, err + } + newest := map[at]Build{} + for _, b := range all { + k := at{b.Module, b.Commit} + if b.Failed != "" || b.Module == "" { + continue + } + if had, has := newest[k]; !has || b.At.After(had.At) || (b.At.Equal(had.At) && b.ID > had.ID) { + newest[k] = b + } + } for k := range out { if out[k].Commit == "" { continue } - id, err := i.BuildOf(ctx, out[k].Module, out[k].Commit) - if err != nil { - return nil, err - } - if id == "" { + b, found := newest[at{out[k].Module, out[k].Commit}] + if !found { continue } - out[k].Build = id - if b, found, err := i.BuildByID(ctx, id); err != nil { - return nil, err - } else if found { - if published := publishedOf(b.Made); published != nil { - out[k].Artifact, out[k].Package = published.Artifact, published.Package - } + out[k].Build = b.ID + if published := publishedOf(b.Made); published != nil { + out[k].Artifact, out[k].Package = published.Artifact, published.Package } } return out, nil