Files
mesh-controller/internal/link/window_test.go
T
jschoubben d0a9abcb1c The store window as a decision, and the problem moving it to the server
introduces

Step 3.4, the consume side's hard part. The guarantee (ADR 0083) is that a
push the controller cannot record because its store is restarting is held
and retried — never dropped, never falsely acknowledged. Keeping the
delivery unacknowledged in memory becomes a nak with a delay: the server
holds it, the controller keeps no list of parked messages, and a controller
that restarts mid-window loses nothing it was holding.

That is a plain win and it introduces one problem. Holding in memory let
the controller drop an older report when a newer one for the same node
arrived, "because acting on it after the newer would undo the newer". A
naked message is the server's and comes back whatever happened meanwhile,
so the older report is redelivered after the newer was applied.

The answer was already in the message. A report carries `Declared`, the
digest of the declaration it is about, which exists because an earlier
attempt to order reports by time lost the race it invited. So supersession
stops being something the controller remembers and becomes something it
checks — the same shape as a node refusing a superseded declaration by
sequence (issue 107): ordering settled by what a message says, not by when
it arrived.

Pure, so the guarantee is testable without a bus, a store or a clock. Nine
tests, including that staleness is decided before the store is waited on —
a redelivery that lost its race must not hold a slot a current message
needs.
2026-09-27 00:05:52 +02:00

85 lines
3.3 KiB
Go

package link
import (
"errors"
"testing"
"time"
)
func window() StoreWindow { return StoreWindow{GiveUpAfter: 2 * time.Minute} }
// The guarantee itself (novox/hq ADR 0083): a push the store cannot record is held, not dropped
// and not falsely acknowledged.
func TestAMessageTheStoreCannotTakeYetIsHeld(t *testing.T) {
if got := window().Decide(ErrTryAgain, "", "", 0); got != Hold {
t.Fatalf("got %v; a push the store could not record was not held", got)
}
}
// A refusal is an answer. Holding it would turn a message the mesh understood into one it
// retries forever.
func TestARefusalIsNotHeld(t *testing.T) {
if got := window().Decide(errors.New("that node does not exist"), "", "", 0); got != Take {
t.Fatalf("got %v; a refusal was mistaken for the store being away", got)
}
}
// Held has a limit, and past it the message is settled rather than held for ever.
func TestAStoreThatNeverComesBackEndsTheHold(t *testing.T) {
if got := window().Decide(ErrTryAgain, "", "", 3*time.Minute); got != GiveUp {
t.Fatalf("got %v; the window has no end", got)
}
if got := window().Decide(ErrTryAgain, "", "", time.Minute); got != Hold {
t.Fatalf("got %v; the window ended early", got)
}
}
// **The problem that holding in the server introduces.** A naked message is redelivered whatever
// else happened meanwhile, so a report about a superseded declaration comes back after the newer
// one was applied — and applying it would undo the newer.
func TestAReportAboutASupersededDeclarationIsNotApplied(t *testing.T) {
got := window().Decide(nil, "digest-of-the-old-one", "digest-of-the-current-one", 0)
if got != Stale {
t.Fatalf("got %v; a redelivery that lost its race would have undone what came after", got)
}
}
func TestAReportAboutTheOutstandingDeclarationIsApplied(t *testing.T) {
if got := window().Decide(nil, "same", "same", 0); got != Take {
t.Fatalf("got %v; a current report was discarded", got)
}
}
// A message that is about no declaration — an enrolment, a build result — is never stale: there
// is nothing for it to be out of date with.
func TestAMessageAboutNoDeclarationIsNeverStale(t *testing.T) {
if got := window().Decide(nil, "", "whatever-is-outstanding", 0); got != Take {
t.Fatalf("got %v; an enrolment was treated as a stale report", got)
}
if got := window().Decide(nil, "a-digest", "", 0); got != Take {
t.Fatalf("got %v; a report was called stale against a node that was sent nothing", got)
}
}
// Staleness is decided before the store is waited on: a redelivery that lost its race must not
// hold a slot in the window that a current message needs.
func TestAStaleMessageIsNotHeldForTheStore(t *testing.T) {
if got := window().Decide(ErrTryAgain, "old", "current", 0); got != Stale {
t.Fatalf("got %v; a superseded message was held for a store it would never be applied to", got)
}
}
// The delay backs off: a store that is gone is not helped by being asked every second, and the
// delay is what keeps a window of held messages from becoming a spin.
func TestRedeliveryBacksOff(t *testing.T) {
first := RedeliverAfter(0)
later := RedeliverAfter(10 * time.Second)
last := RedeliverAfter(time.Minute)
if !(first < later && later < last) {
t.Fatalf("delays do not back off: %v %v %v", first, later, last)
}
if last > 30*time.Second {
t.Fatalf("a held message waits %v between attempts, which is longer than a store restart", last)
}
}