Monitor restarts itself when the service updated its binary on disk
This commit is contained in:
@@ -1034,6 +1034,9 @@ type statusSnapshot struct {
|
||||
UptimeS int64 `json:"uptime_s"`
|
||||
Downstreams []statusDownstream `json:"downstreams"`
|
||||
Lock statusLock `json:"lock"`
|
||||
// MonitorNote is set client-side (never over the wire) when the
|
||||
// monitor's own binary differs from the service's version.
|
||||
MonitorNote string `json:"-"`
|
||||
}
|
||||
|
||||
// statusProvider assembles the one-line JSON snapshot for CmdStatus.
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -21,7 +22,10 @@ const (
|
||||
)
|
||||
|
||||
// monitorCommand renders a live status view of the running service,
|
||||
// refreshed every second from the control channel. Ctrl+C quits.
|
||||
// refreshed every second from the control channel. When the service
|
||||
// reports a different version and the executable on disk changed (the
|
||||
// updater replaced it), the monitor restarts itself onto the new binary.
|
||||
// Ctrl+C quits.
|
||||
func monitorCommand() int {
|
||||
if !stdoutIsTerminal() {
|
||||
fmt.Fprintln(os.Stderr, "gpu-turnstile: --monitor needs an interactive terminal")
|
||||
@@ -30,12 +34,26 @@ func monitorCommand() int {
|
||||
enableVirtualTerminal()
|
||||
fmt.Print("\x1b[2J") // clear once; frames then redraw in place
|
||||
defer fmt.Print(cReset + "\n")
|
||||
exe, _ := os.Executable()
|
||||
var exeStamp time.Time
|
||||
if st, err := os.Stat(exe); err == nil {
|
||||
exeStamp = st.ModTime()
|
||||
}
|
||||
for {
|
||||
frame := renderWaiting()
|
||||
if reply, err := control.Ask(control.CmdStatus); err == nil {
|
||||
if msg, ok := strings.CutPrefix(reply, "OK "); ok {
|
||||
var snap statusSnapshot
|
||||
if json.Unmarshal([]byte(msg), &snap) == nil {
|
||||
if snap.Version != "" && snap.Version != version {
|
||||
if exeChanged(exe, exeStamp) {
|
||||
fmt.Print("\x1b[2J\x1b[H")
|
||||
fmt.Printf("gpu-turnstile: service updated to %s — restarting the monitor\n", snap.Version)
|
||||
restartSelf(exe, "--monitor")
|
||||
return 0
|
||||
}
|
||||
snap.MonitorNote = fmt.Sprintf("note: the service runs %s, this monitor is %s", snap.Version, version)
|
||||
}
|
||||
frame = renderMonitor(snap, termWidth())
|
||||
}
|
||||
}
|
||||
@@ -45,6 +63,24 @@ func monitorCommand() int {
|
||||
}
|
||||
}
|
||||
|
||||
// exeChanged reports whether the executable on disk was replaced since the
|
||||
// recorded stamp (the updater swaps it via rename, which changes ModTime).
|
||||
func exeChanged(exe string, stamp time.Time) bool {
|
||||
if exe == "" || stamp.IsZero() {
|
||||
return false
|
||||
}
|
||||
st, err := os.Stat(exe)
|
||||
return err == nil && !st.ModTime().Equal(stamp)
|
||||
}
|
||||
|
||||
// restartSelf starts a fresh copy of this executable with the given args on
|
||||
// the same console; the caller exits right after.
|
||||
func restartSelf(exe string, args ...string) {
|
||||
cmd := exec.Command(exe, args...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
cmd.Start() //nolint:errcheck // best effort: on failure we just exit
|
||||
}
|
||||
|
||||
func renderWaiting() string {
|
||||
return cDim + " gpu-turnstile — waiting for a running service…" + cReset + "\x1b[K\n"
|
||||
}
|
||||
@@ -81,6 +117,9 @@ func renderMonitor(snap statusSnapshot, width int) string {
|
||||
b.WriteString(fmt.Sprintf(" Queue: %s%d image job(s) waiting%s\x1b[K\n",
|
||||
cYellow, snap.Lock.ImageQueue, cReset))
|
||||
}
|
||||
if snap.MonitorNote != "" {
|
||||
b.WriteString(" " + cYellow + snap.MonitorNote + cReset + "\x1b[K\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user