Say the node tools are there, every minute (hq to-be 45 Phase 1, S11)
A machine whose node-engine is heard and whose runtime is gone is a machine nobody can ask anything, and nothing said so. The runtime now says on mesh.control.<node>.tools-alive, every minute, that it is there, with its interval and build; the controller raises tools-silent after three missed. Core NATS, like the host's heartbeat: a lost one is the next one. A heartbeat the bus refuses is logged when that starts and when it stops.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// Package alive is the node tools saying they are there (novox/hq to-be 45 §3, S11).
|
||||
//
|
||||
// **A machine heard and its runtime gone is a machine nobody can ask anything.** The node-engine says
|
||||
// every minute that the machine is there; nothing said whether the runtime serving every module's
|
||||
// tools and every held seat's verbs was. Now it says so too, on its own subject, every interval, with
|
||||
// the interval — the controller's watchdog raises `tools-silent` after three missed — and its build.
|
||||
// Core NATS, kept by nobody, as the host's heartbeat: a lost one is the next one.
|
||||
package alive
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Every is how often the runtime says it is there.
|
||||
const Every = 60 * time.Second
|
||||
|
||||
// Subject is where one machine's node tools say it.
|
||||
func Subject(node string) string { return "mesh.control." + node + ".tools-alive" }
|
||||
|
||||
// Beat is what is said: the machine, the interval, and the build.
|
||||
type Beat struct {
|
||||
Node string `json:"node"`
|
||||
IntervalSeconds int `json:"interval_seconds"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// Sayer publishes one message, kept by nobody.
|
||||
type Sayer interface {
|
||||
Say(subject string, body []byte) error
|
||||
}
|
||||
|
||||
// Version is this build's commit, as the toolchain stamped it; empty when it did not.
|
||||
func Version() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
for _, s := range info.Settings {
|
||||
if s.Key == "vcs.revision" {
|
||||
return s.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Keep says the runtime is there now and every interval until the returned function is called. A
|
||||
// heartbeat that cannot be said is logged when that starts and when it stops — not every minute.
|
||||
func Keep(bus Sayer, node string, every time.Duration, logf func(string, ...any)) func() {
|
||||
body, _ := json.Marshal(Beat{Node: node, IntervalSeconds: int(every / time.Second), Version: Version()})
|
||||
done := make(chan struct{})
|
||||
var once sync.Once
|
||||
go func() {
|
||||
failing := ""
|
||||
tick := time.NewTicker(every)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
why := ""
|
||||
if err := bus.Say(Subject(node), body); err != nil {
|
||||
why = err.Error()
|
||||
}
|
||||
if why != failing {
|
||||
if why != "" {
|
||||
logf("node-tools: could not say this runtime is there — the mesh will call it silent: %s", why)
|
||||
} else {
|
||||
logf("node-tools: says again that this runtime is there")
|
||||
}
|
||||
failing = why
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-tick.C:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return func() { once.Do(func() { close(done) }) }
|
||||
}
|
||||
Reference in New Issue
Block a user