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.
61 lines
2.3 KiB
Go
61 lines
2.3 KiB
Go
package link
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// heldControl is a merge announcement that records how it was settled.
|
|
type heldControl struct {
|
|
body []byte
|
|
took, held, drops int
|
|
}
|
|
|
|
func (c *heldControl) Kind() string { return "source-moved" }
|
|
func (c *heldControl) Body() []byte { return c.body }
|
|
func (c *heldControl) Subject() string { return "" }
|
|
func (c *heldControl) Redelivered() bool { return false }
|
|
func (c *heldControl) HeldFor() time.Duration { return 0 }
|
|
func (c *heldControl) Answer(context.Context, []byte) error { return nil }
|
|
func (c *heldControl) Took() error { c.took++; return nil }
|
|
func (c *heldControl) Hold(time.Duration) error { c.held++; return nil }
|
|
func (c *heldControl) Drop() error { c.drops++; return nil }
|
|
|
|
type sourceMover struct {
|
|
Upgrader
|
|
answer error
|
|
}
|
|
|
|
func (s sourceMover) SourceMoved(context.Context, SourceMoved) error { return s.answer }
|
|
|
|
// **A merge is acknowledged only after the controller recorded it** (novox/hq ADR 0407 decision 3, issue 1092): an
|
|
// answer of "not now" — the plan it belongs to could not be saved, the bus away, the lease lost — leaves the announcement
|
|
// unacknowledged, held to be handed over again; only an answer that is not "not now" settles it.
|
|
func TestAMergeTheControllerCouldNotRecordIsHeldAndNotAcknowledged(t *testing.T) {
|
|
body, err := json.Marshal(SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "c1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := &Server{log: log.New(io.Discard, "", 0)}
|
|
for name, tc := range map[string]struct {
|
|
answer error
|
|
took, held int
|
|
}{
|
|
"recorded": {nil, 1, 0},
|
|
"not now": {errors.Join(ErrTryAgain, errors.New("the plan is not saved")), 0, 1},
|
|
"an answer, not a wait": {errors.New("the mesh could not act on it"), 1, 0},
|
|
} {
|
|
s.upgrader = sourceMover{answer: tc.answer}
|
|
m := &heldControl{body: body}
|
|
s.sourceMoved(context.Background(), m)
|
|
if m.took != tc.took || m.held != tc.held || m.drops != 0 {
|
|
t.Errorf("%s: acknowledged %d times and held %d times, want %d and %d", name, m.took, m.held, tc.took, tc.held)
|
|
}
|
|
}
|
|
}
|