Hold a group's batch open for its last member and keep both kinds of member in one batch and one plan (issue 977, part C, ADR 0418)
This commit is contained in:
@@ -181,7 +181,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
}
|
||||
// **Its delivery group's word, before the plans are held** (ADR 0306 decision 2): a slow answer holds up no walk.
|
||||
own := ownPath(touches)
|
||||
waits, err := groupWait(ctx, inv, m, deliverySeatHeld(entries), own)
|
||||
waits, holding, err := groupWaitHolding(ctx, inv, m, deliverySeatHeld(entries))
|
||||
if err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
@@ -237,6 +237,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
if added, err := inv.AddMerge(ctx, heard); err != nil || !added {
|
||||
return notNow(err)
|
||||
}
|
||||
holdFor(&batch, m.Head, waits, holding, own, now)
|
||||
if err := keepBatch(ctx, inv, &batch, now, ""); err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
@@ -245,6 +246,43 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
return cutBatchesHeld(ctx, f.open, now)
|
||||
}
|
||||
|
||||
// holdFor keeps in a batch that a merge of a group joined whether that group still has a member to merge (ADR 0418
|
||||
// clause 3): held while it has, the window running again from the moment the last hold is cleared. A group with a
|
||||
// merge of the controller's own path makes its batch an own-path one: such a plan waits for nobody's word, and is
|
||||
// started by the controller when the group's batch closes (clause 6).
|
||||
func holdFor(batch *inventory.Plan, head, why string, holding, own bool, now time.Time) {
|
||||
if batch.Delivery == nil {
|
||||
batch.Delivery = &inventory.PlanDelivery{}
|
||||
}
|
||||
if batch.Delivery.Batch == nil {
|
||||
batch.Delivery.Batch = &inventory.PlanBatch{Own: own}
|
||||
}
|
||||
w := batch.Delivery.Batch
|
||||
w.Own = w.Own || own
|
||||
if head == "" {
|
||||
return
|
||||
}
|
||||
if holding {
|
||||
if w.Holds == nil {
|
||||
w.Holds = map[string]string{}
|
||||
}
|
||||
w.Holds[head] = why
|
||||
return
|
||||
}
|
||||
if _, held := w.Holds[head]; held {
|
||||
holds := map[string]string{}
|
||||
for h, y := range w.Holds {
|
||||
if h != head {
|
||||
holds[h] = y
|
||||
}
|
||||
}
|
||||
w.Holds = holds
|
||||
if len(holds) == 0 {
|
||||
w.Holds, w.HeldAt = nil, now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// owedLate says a merge that reads as history is still the record's to answer (review of this change): a cut
|
||||
// marks every module it moved seen at that moment, so a merge of that branch
|
||||
// handed over late — made before the cut, heard after it — reads as history and was dropped. One that touches
|
||||
@@ -349,6 +387,15 @@ func batchToJoin(ctx context.Context, inv *inventory.Inventory, heard inventory.
|
||||
if err != nil {
|
||||
return inventory.Plan{}, "", err
|
||||
}
|
||||
// **A group is one batch, whatever kinds its members are** (novox/hq ADR 0418 clause 6): a merge joins the batch
|
||||
// of the other kind that holds a merge from its head.
|
||||
if group, ok, err := batchOfGroup(ctx, inv, announcedOf(heard).Head, heard.Repository); err != nil {
|
||||
return inventory.Plan{}, "", err
|
||||
} else if ok && group.OwnPath() != own && group.Delivery != nil && group.Delivery.Batch != nil {
|
||||
group.Delivery.Batch.CutAtOnce = false
|
||||
return group, "its group " + announcedOf(heard).Head + " has a merge in " + group.ID + ", a batch of the other kind: " +
|
||||
"a group is one batch", nil
|
||||
}
|
||||
joins, whys, _, err := relatedBatches(ctx, inv, []inventory.BatchedMerge{heard}, own, "", entries, read, edges)
|
||||
if err != nil {
|
||||
return inventory.Plan{}, "", err
|
||||
@@ -572,18 +619,25 @@ func keepBatchBecause(ctx context.Context, inv *inventory.Inventory, b *inventor
|
||||
if b.Delivery == nil {
|
||||
b.Delivery = &inventory.PlanDelivery{}
|
||||
}
|
||||
closes, latest := windowOf(merges, b.Created, window, atMost)
|
||||
anchor := b.Created
|
||||
if was := b.Delivery.Batch; was != nil && was.HeldAt.After(anchor) {
|
||||
anchor = was.HeldAt // the maximum counts from the last hold's end (ADR 0418 clause 3)
|
||||
}
|
||||
closes, latest := windowOf(merges, anchor, window, atMost)
|
||||
b.Delivery.Merges = named
|
||||
var closed, cutAtOnce bool
|
||||
var withheld []string
|
||||
var waits string
|
||||
var holds map[string]string
|
||||
var heldAt time.Time
|
||||
if was := b.Delivery.Batch; was != nil {
|
||||
holds, heldAt = was.Holds, was.HeldAt
|
||||
closed, withheld = was.Closed, was.Withheld // a stop's, which no look takes back (novox/hq ADR 0299)
|
||||
cutAtOnce = was.CutAtOnce // the merge's, until another joins it (ADR 0304 decision 1)
|
||||
waits = was.Waits // its group's, as said when it was made (ADR 0306 decision 2)
|
||||
}
|
||||
b.Delivery.Batch = &inventory.PlanBatch{ClosesAt: closes, AtMost: latest, Behind: behind, BehindWhy: why, Own: b.OwnPath(),
|
||||
Closed: closed, Withheld: withheld, CutAtOnce: cutAtOnce, Waits: waits}
|
||||
Closed: closed, Withheld: withheld, CutAtOnce: cutAtOnce, Waits: waits, Holds: holds, HeldAt: heldAt}
|
||||
b.State = inventory.PlanAssembling
|
||||
if behind != "" && windowClosed(*b, now) {
|
||||
b.State = inventory.PlanQueued
|
||||
@@ -619,13 +673,20 @@ func windowOf(merges []inventory.BatchedMerge, created time.Time, window, atMost
|
||||
}
|
||||
|
||||
// windowClosed says a batch's window is closed: closed by a stop, closed at once for a merge that shares
|
||||
// nothing (ADR 0304 decision 1), no merge for the window's length, or its maximum reached.
|
||||
// nothing (ADR 0304 decision 1), no merge for the window's length, or its maximum reached. **A batch holding a group
|
||||
// with a member still to merge is closed by none of the last three** (novox/hq ADR 0418 clause 3).
|
||||
func windowClosed(b inventory.Plan, now time.Time) bool {
|
||||
if b.Delivery == nil || b.Delivery.Batch == nil {
|
||||
return true
|
||||
}
|
||||
w := b.Delivery.Batch
|
||||
return w.Closed || w.CutAtOnce || !now.Before(w.ClosesAt) || !now.Before(w.AtMost)
|
||||
if w.Closed {
|
||||
return true
|
||||
}
|
||||
if len(w.Holds) > 0 {
|
||||
return false // a group's last member is still to merge: neither the window nor its maximum closes the batch
|
||||
}
|
||||
return w.CutAtOnce || !now.Before(w.ClosesAt) || !now.Before(w.AtMost)
|
||||
}
|
||||
|
||||
// carriedOf is a set of merges as a walk carries them: the latest merge of each repository's branch, and
|
||||
@@ -789,6 +850,11 @@ func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// **A hold is looked at again at every cut** (ADR 0418 clause 3): a group whose last member merged or closed
|
||||
// since is released, and its window runs again from now.
|
||||
if batches, err = releaseHolds(ctx, inv, batches, now); err != nil {
|
||||
return err
|
||||
}
|
||||
keepAll := func(behind string) error {
|
||||
for i := range batches {
|
||||
if err := keepBatch(ctx, inv, &batches[i], now, behind); err != nil {
|
||||
@@ -812,6 +878,41 @@ func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
return cutUnrelated(ctx, open, batches, started, waiting, now)
|
||||
}
|
||||
|
||||
// releaseHolds asks mesh-delivery again about each group a batch holds, and clears the holds of those with no member
|
||||
// left to merge. A group mesh-delivery cannot be asked about stays held: the safe error is to wait. It answers the
|
||||
// batches as kept.
|
||||
func releaseHolds(ctx context.Context, inv *inventory.Inventory, batches []inventory.Plan, now time.Time) ([]inventory.Plan, error) {
|
||||
var entries []inventory.Entry
|
||||
for i := range batches {
|
||||
b := &batches[i]
|
||||
if b.Delivery == nil || b.Delivery.Batch == nil || len(b.Delivery.Batch.Holds) == 0 {
|
||||
continue
|
||||
}
|
||||
if entries == nil {
|
||||
var err error
|
||||
if entries, err = inv.Catalogued(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
changed := false
|
||||
for head := range b.Delivery.Batch.Holds {
|
||||
open, isGroup, err := openMembersOf(ctx, inv, deliverySeatHeld(entries), head, "")
|
||||
if err != nil || (isGroup && len(open) > 0) {
|
||||
continue
|
||||
}
|
||||
holdFor(b, head, "", false, false, now)
|
||||
changed = true
|
||||
fmt.Printf("%s: group %s has no member left to merge; its window runs again from now\n", b.ID, head)
|
||||
}
|
||||
if changed {
|
||||
if err := keepBatch(ctx, inv, b, now, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return batches, nil
|
||||
}
|
||||
|
||||
// cutUnrelated cuts the closed batches that share nothing with an open walk, each into a walk beside the open
|
||||
// ones, and says the others queued behind the first walk they are related to (novox/hq ADR 0407 decision 4,
|
||||
// issue 973). A batch cut in this look counts as open for the batches after it.
|
||||
|
||||
Reference in New Issue
Block a user