Keep rollouts and the merges they answer in the rollouts bucket (issue 1092)
Once chain_moved records the kind, a plan is a key of the rollouts bucket under its id and the open batch is the same key in state assembling. A save refuses a copy another save moved since (the plan's own Revision, kept in its value, counts saves) and keeps the merges the record holds, so a copy read before a merge moved cannot lay it back. The merges a batch or walk answers are the record's held field, with the columns the table had; a merge no plan answers stays, flagged, in the record that held it last, or in the record kept for those. A merge moved to a plan is written to the receiving record first and then taken from the giving one; one found in both is read from the one it entered last and repaired at the next cut. Plans and merges are read from a view kept by one watch of the bucket. A bus that does not answer, a lease lost and a key moved are now not-now like the store being away, so a merge whose plan could not be written is held and handed over again, never acknowledged. The start moves release_plan and batched_merge into the bucket, one kind, counted against both tables.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -121,9 +122,97 @@ func (i *Inventory) movers() []mover {
|
||||
return n, err
|
||||
},
|
||||
lock: `lock table build, build_request in share mode`,
|
||||
}, {
|
||||
kind: KindRollouts,
|
||||
rows: func(ctx context.Context) ([]moveRow, int, error) {
|
||||
plans, err := i.plans(ctx, `order by created`)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
merges, err := i.merges(ctx, ``)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
tablePlans := len(plans)
|
||||
if plans, err = holdMerges(plans, merges); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]moveRow, 0, len(plans))
|
||||
for _, p := range plans {
|
||||
body, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out = append(out, moveRow{key: p.ID, value: p, size: len(body)})
|
||||
}
|
||||
return out, tablePlans + len(merges), nil
|
||||
},
|
||||
create: func(ctx context.Context, c *chain.Chain, row moveRow) error {
|
||||
_, err := chain.RolloutsOf[Plan](c).Create(ctx, row.key, row.value.(Plan))
|
||||
return err
|
||||
},
|
||||
keys: func(ctx context.Context, c *chain.Chain) ([]string, error) {
|
||||
return chain.RolloutsOf[Plan](c).Keys(ctx)
|
||||
},
|
||||
count: func(ctx context.Context, tx pgx.Tx) (int, error) {
|
||||
var n int
|
||||
err := tx.QueryRow(ctx, `select (select count(*) from release_plan) + (select count(*) from batched_merge)`).Scan(&n)
|
||||
return n, err
|
||||
},
|
||||
lock: `lock table release_plan, batched_merge in share mode`,
|
||||
}}
|
||||
}
|
||||
|
||||
// holdMerges puts each batched merge into the record of the plan that answers it. A merge no plan answers — the plan_id
|
||||
// of a merge a failed walk carried is null, or names a plan the store never kept — is held, flagged waiting, by the
|
||||
// newest plan that lists it among its merges, else the record kept for the merges no plan answers: the record that held it
|
||||
// last.
|
||||
func holdMerges(plans []Plan, merges []BatchedMerge) ([]Plan, error) {
|
||||
waitingAt := -1
|
||||
at := make(map[string]int, len(plans))
|
||||
for k, p := range plans {
|
||||
at[p.ID] = k
|
||||
}
|
||||
newest := func(match func(Plan) bool) int {
|
||||
best := -1
|
||||
for k, p := range plans {
|
||||
if match(p) && (best < 0 || p.Created.After(plans[best].Created)) {
|
||||
best = k
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
for _, m := range merges {
|
||||
held := heldOf(m, m.Heard)
|
||||
holder, answered := at[m.Plan]
|
||||
if m.Plan == "" || !answered {
|
||||
held.Waiting = true
|
||||
holder = newest(func(p Plan) bool {
|
||||
if p.Delivery == nil {
|
||||
return false
|
||||
}
|
||||
for _, listed := range p.Delivery.Merges {
|
||||
if strings.EqualFold(listed.Repository, m.Repository) && listed.Commit == m.Commit {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if holder < 0 {
|
||||
// Held by the record kept for the merges no plan answers, made with the others.
|
||||
if waitingAt < 0 {
|
||||
plans = append(plans, Plan{ID: WaitingRecord, State: "merges", Created: time.Unix(0, 0).UTC(), Revision: 1,
|
||||
Tiers: [][]string{}, Modules: map[string]*PlanModule{}, Note: "the merges no plan answers"})
|
||||
waitingAt = len(plans) - 1
|
||||
}
|
||||
holder = waitingAt
|
||||
}
|
||||
}
|
||||
plans[holder].Held = append(plans[holder].Held, held)
|
||||
}
|
||||
return plans, nil
|
||||
}
|
||||
|
||||
// MoveChain moves every kind not moved yet, in order, and says what it did. It needs the chain attached and the lease
|
||||
// held (the chain's writes ask). It stops at the first kind that fails.
|
||||
func (i *Inventory) MoveChain(ctx context.Context, say func(format string, args ...any)) ([]MoveReport, error) {
|
||||
|
||||
Reference in New Issue
Block a user