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.
81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
// 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) }) }
|
|
}
|