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.
72 lines
2.4 KiB
Go
72 lines
2.4 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/novox/mesh-controller/internal/chain"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// refusing is a bucket that refuses to write while it is told to.
|
|
type refusing struct {
|
|
chain.Backend
|
|
refuse atomic.Bool
|
|
}
|
|
|
|
func (r *refusing) Create(ctx context.Context, key string, value []byte) (uint64, error) {
|
|
if r.refuse.Load() {
|
|
return 0, nats.ErrTimeout
|
|
}
|
|
return r.Backend.Create(ctx, key, value)
|
|
}
|
|
|
|
func (r *refusing) Update(ctx context.Context, key string, value []byte, revision uint64) (uint64, error) {
|
|
if r.refuse.Load() {
|
|
return 0, nats.ErrTimeout
|
|
}
|
|
return r.Backend.Update(ctx, key, value, revision)
|
|
}
|
|
|
|
// **A merge whose plan cannot be saved is held, recorded nowhere, and recorded when it is handed over again** (ADR 0407
|
|
// decision 3, issue 1092): hearMerge answers ErrTryAgain — the bus leaves the announcement unacknowledged and hands it
|
|
// over again — and the merge is in no record until the plan holding it is written.
|
|
func TestAMergeWhosePlanCannotBeSavedIsHeldAndRecordedWhenHandedOverAgain(t *testing.T) {
|
|
open := windowed(t)
|
|
everyHeadWaits(t)
|
|
asksWithPaths(t)
|
|
ctx := t.Context()
|
|
rollouts := &refusing{Backend: chain.NewMemory()}
|
|
open.inventory.UseChain(chain.New(chain.NewMemory(), rollouts, chain.NewMemory(), func(context.Context) error { return nil }))
|
|
merge := catalogueMerge("cat1aaaaaaaa", "app", t0)
|
|
|
|
rollouts.refuse.Store(true)
|
|
err := (following{open: open}).hearMerge(ctx, merge, t0.Add(time.Second))
|
|
if !errors.Is(err, link.ErrTryAgain) {
|
|
t.Fatalf("a merge whose plan could not be saved answered %v, which the bus takes as done", err)
|
|
}
|
|
if _, known, err := open.inventory.MergeOf(ctx, "novox/mesh-catalog", merge.Commit); err != nil || known {
|
|
t.Fatalf("the merge is kept though its plan was not: %v %v", known, err)
|
|
}
|
|
if _, bs := walks(t, open); len(bs) != 0 {
|
|
t.Fatalf("a batch was kept though its write failed: %+v", bs)
|
|
}
|
|
|
|
rollouts.refuse.Store(false)
|
|
hear(t, open, merge, t0.Add(10*time.Second))
|
|
_, bs := walks(t, open)
|
|
if len(bs) != 1 {
|
|
t.Fatalf("the merge handed over again made %d batch(es)", len(bs))
|
|
}
|
|
if m, known, _ := open.inventory.MergeOf(ctx, "novox/mesh-catalog", merge.Commit); !known || m.Plan != bs[0].ID {
|
|
t.Fatalf("the merge is %+v %v, and the batch %s", m, known, bs[0].ID)
|
|
}
|
|
}
|