mesh/delivery-group group feat/335-a-busy-container-is-replaced-when-idle rejected: its order contradicts itself: novox/mesh-catalog@190608193764, novox/mesh-c…
mesh/merge-gate out of date: judged against trunk 7a41e120635b, the trunk is now 8431ac96562e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 7a41e120635b, the trunk is now 8431ac96562e; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
A send that replaced the build agent killed the check it ran, and the queue started it again elsewhere (issue 594). A container may now declare busy; the node-engine asks it before replacing it, applies everything else, says the wait, and replaces it on the first idle pass or at the bound, kept across restarts (novox/hq ADR 0335).
67 lines
2.6 KiB
Go
67 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/link"
|
|
)
|
|
|
|
// A container waiting because it said busy is news when it starts waiting and when it is replaced, never on
|
|
// its output alone (novox/hq ADR 0335): the walk waiting on this machine starts its gate at the report that
|
|
// says it replaced, and a reconcile is otherwise silent.
|
|
func TestABusyWaitIsSaidWhenItStartsAndWhenItEnds(t *testing.T) {
|
|
quiet := link.Report{Outward: []string{"eth0"}}
|
|
waiting := link.Report{Outward: []string{"eth0"}, Busy: []link.Busy{{ID: "build-agent.server",
|
|
Container: "mesh-build-agent", State: "waiting", Said: "building a"}}}
|
|
still := link.Report{Outward: []string{"eth0"}, Busy: []link.Busy{{ID: "build-agent.server",
|
|
Container: "mesh-build-agent", State: "waiting", Said: "building b, a minute on"}}}
|
|
replaced := link.Report{Outward: []string{"eth0"}, Busy: []link.Busy{{ID: "build-agent.server",
|
|
Container: "mesh-build-agent", State: "replaced-when-idle"}}}
|
|
if adoptionFingerprint(waiting) == adoptionFingerprint(quiet) {
|
|
t.Error("a container starting to wait is not news")
|
|
}
|
|
if adoptionFingerprint(still) != adoptionFingerprint(waiting) {
|
|
t.Error("a wait whose output changed reads as news on every ask")
|
|
}
|
|
if adoptionFingerprint(replaced) == adoptionFingerprint(waiting) {
|
|
t.Error("a container replaced after it waited is not news")
|
|
}
|
|
if !worthSaying(link.Report{Busy: replaced.Busy}) {
|
|
t.Error("a replacement after a wait is not worth saying")
|
|
}
|
|
}
|
|
|
|
// While a container waits, a pass is asked for every so often, not at the reconcile's five minutes; with
|
|
// nothing waiting, none (novox/hq ADR 0335 §2).
|
|
func TestAWaitIsAskedAgainSoonerThanTheReconcile(t *testing.T) {
|
|
if BusyAskEvery >= ReconcileEvery || BusyAskEvery > time.Minute {
|
|
t.Fatalf("a waiting container is asked every %s", BusyAskEvery)
|
|
}
|
|
var reconciles atomic.Int32
|
|
queue := &link.Queue{Reconcile: func(context.Context) (link.Report, bool) {
|
|
reconciles.Add(1)
|
|
return link.Report{}, false
|
|
}}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go queue.Run(ctx)
|
|
busyWaiting.Store(false)
|
|
t.Cleanup(func() { busyWaiting.Store(false) })
|
|
go busyPasses(ctx, queue, 10*time.Millisecond)
|
|
time.Sleep(100 * time.Millisecond)
|
|
if n := reconciles.Load(); n != 0 {
|
|
t.Fatalf("%d passes were asked with nothing waiting", n)
|
|
}
|
|
busyWaiting.Store(true)
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for reconciles.Load() == 0 && time.Now().Before(deadline) {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if reconciles.Load() == 0 {
|
|
t.Fatal("no pass was asked while a container waits")
|
|
}
|
|
}
|