Files
mesh-delivery/modules/mesh-delivery-watchman/internal/watch/mirror.go
T

151 lines
5.0 KiB
Go

package watch
import (
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// A build ask taken by a node is a move (novox/hq ADR 0525 §4, issues 1887 A8b, 1908, 1909): the node writes the ask's step
// key `<node>.<ask id>` running in the build module's steps (delivery.StepsBucket) when it takes the ask, and writes it passed,
// failed or refused when the ask ends. The pulse of the move is the pulse of the bucket named as the seat's stream
// (delivery.AskStream), under the ask's id, and it begins when the node started the step.
//
// **An ask still waiting in the stream is a backlog, not a stall** (issue 1908): the rebuild after the switch puts about 170
// asks on a few nodes' slots, and most wait for hours with every worker healthy. No pulse is made for a waiting ask, so none
// can expire into a false stuck row; the view shows the backlog and the oldest waiting ask's age (delivery view, A8a). What
// is read of the steps at the watchman's start is mirrored the same way, so an ask running when the watchman was down is
// pulsed again (issue 1909).
// Step is a change of a key of the steps bucket of language's build module, read from the watch (at the start the keys that
// stand, then each change). A running step makes the pulse of its ask, from the step's own start; a step that ended, or a
// key deleted, ends it. The node's `paused.<node>` key, a key that is no step key and a value that cannot be read are left.
func (w *Watchman) Step(language, key string, value []byte, op Op) {
bucket, err := delivery.AskStream(language)
if err != nil {
w.say("no pulse for the step %s: %v", key, err)
return
}
node, id, err := delivery.ParseStepKey(key)
if err != nil {
return
}
w.mu.Lock()
defer w.mu.Unlock()
switch op {
case Expired:
return
case Delete:
w.moved(bucket, id)
return
}
step, err := delivery.DecodeStep(value)
if err != nil {
w.say("the step %s cannot be read, and is left: %v", key, err)
return
}
if step.State != delivery.StepRunning {
w.moved(bucket, id)
return
}
since := step.Started
if since.IsZero() {
since = w.now()
}
w.mirrorWithin(w.stepBound, bucket, id, describe("the ask %s to end on %s", id, node), node, since, 0)
}
// A want is a move too (novox/hq ADR 0539 §19, issue 1906): the assigner writes `want.<node>.<module>` in its wants bucket,
// and the node's agent owes the node-engine's have naming the want's fingerprint. The pulse is the pulse of that bucket under
// the want's key, owed by the node's agent, and it begins when the assigner wrote the want. The have naming the fingerprint
// ends it; a want that is removed, or a key deleted, ends it as well. Nothing renews it: the bound is how long a node may
// take to apply a want, the same as a build step's (StepBound). Keys of that bucket that are not a want of a module (a hold,
// and the node's own key) are left.
// Want is a change of a key of the assigner's wants bucket, read from the watch (at the start the keys that stand, then each
// change), at the time the entry was written.
func (w *Watchman) Want(key string, value []byte, op Op, at time.Time) {
node, module, err := delivery.ParseWantKey(key)
if err != nil {
return
}
w.mu.Lock()
defer w.mu.Unlock()
switch op {
case Expired:
return
case Delete:
delete(w.wants, key)
w.moved(delivery.AssignerWantsBucket, key)
return
}
want, err := delivery.DecodeWant(value)
if err != nil {
w.say("the want %s cannot be read, and is left: %v", key, err)
return
}
if want.Removed {
delete(w.wants, key)
w.moved(delivery.AssignerWantsBucket, key)
return
}
w.wants[key] = want.FP
if w.have(node, module) == want.FP {
w.moved(delivery.AssignerWantsBucket, key)
return
}
if at.IsZero() {
at = w.now()
}
w.mirrorWithin(w.stepBound, delivery.AssignerWantsBucket, key,
describe("the node agent of %s to run %s at %s", node, module, shortFP(want.FP)), want.FP, at, 0)
}
// Have is a change of a key of the node-engine's have bucket: the fingerprint a node runs of a module. One that names the
// fingerprint of the node's want for the module ends the want's pulse. A node's own key and a key deleted are left.
func (w *Watchman) Have(key string, value []byte, op Op) {
node, module, isNode, err := delivery.ParseHaveKey(key)
if err != nil || isNode {
return
}
w.mu.Lock()
defer w.mu.Unlock()
switch op {
case Expired:
return
case Delete:
delete(w.haves, key)
return
}
have, err := delivery.DecodeHave(value)
if err != nil {
w.say("the have %s cannot be read, and is left: %v", key, err)
return
}
w.haves[key] = have.FP
wk, err := delivery.WantKey(node, module)
if err != nil {
return
}
if fp, ok := w.wants[wk]; ok && fp == have.FP {
w.moved(delivery.AssignerWantsBucket, wk)
}
}
func (w *Watchman) have(node, module string) string {
key, err := delivery.HaveKey(node, module)
if err != nil {
return ""
}
return w.haves[key]
}
func shortFP(fp string) string {
hex := strings.TrimPrefix(fp, delivery.SourcePrefix2)
if len(hex) > 12 {
hex = hex[:12]
}
return hex
}