// 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) }) } }